domingo, 10 de febrero de 2008

brújula

Voy a poner una brújula gráfica que indique las salidas de cada localidad. Para aligerar en la medida de lo posible la aparición de vocabulario repetitivo en las descripciones: cosas como "puedes ir hacia el Norte..." o un listado de salidas no requerido por el jugador con el comando pertinente.

Aunque en muchos casos será inevitable que aparezcan puntos cardinales en la descripción. De otro modo, si por ejemplo a lo lejos vemos un molino, sin más, podríamos tener que pasar por otros sitios antes de -por descarte- saber que la dirección que conduce a él es noroeste.

Para lugares sin referencias lejanas ni caminos, como un bosque cerrado, funcionará bien.
-Ahorra texto repetitivo en la descripción.
-Ahorra tener que escribir el comando salidas.
-Ahorra jugar a los coches de choque para buscar salidas.

Primero hice un par de diseños que se basaban en dibujar un centro y en torno a él las diversas ramas de las direcciones, una a una en función de que existieran esas salidas.

Pero finalmente he optado por poner un gráfico con la brújula entera, con todos los puntos cardinales, arriba y abajo, y entrar y salir; y oscurecer con PNG transparentes las salidas no disponibles. Es más sencillo y me permite cambiar los colores de la brújula editando un único gráfico.

Para ello creo una ventana gráfica nueva en un lateral: gg_ventana2, y aprovecho la librería NewFlags.h, que por otro lado voy a utilizar para más cosas.
La función que dibuja la brújula será llamada cada vez que se repinten los gráficos, al cambiar de localidad, o manualmente cuando un evento modifique las salidas.

[dibujabrujula i loc;
loc = location;

!dibujo la brújula entera:
glk_image_draw(gg_ventana2,GRAF_brujula_base,5,15);

!pongo a 0 los flags que almacenarán las salidas viables:
for(i=10:i<22:i++)ClearFlag(i);

!busco direcciones viables... y VISIBLES,
!parte de código copiado de la rutina de listado de salidas de la librería en español:
if(location~=TheDark)
objectloop (i in compass)
{
if (loc provides (i.door_dir) && metaclass (loc.(i.door_dir)) ~= nothing or string)
{
switch(i){!sw
nw_obj: setflag(15);
ne_obj: setflag(14);
sw_obj: setflag(17);
se_obj: setflag(16);

n_obj: setflag(10);
s_obj: setflag(11);
e_obj: setflag(12);
w_obj: setflag(13);

in_obj: setflag(18);
out_obj: setflag(19);
u_obj: setflag(20);
d_obj: setflag(21);
}!sw
}
}

!sombreo las direcciones no disponibles con diversos PNG trasnparentes
!ajustados a las formas de cada parte de la brújula:
for(i=10:i<22:i++){
if(Flagoff(i))switch (i){!sww
10: glk_image_draw(gg_ventana2,GRAF_brujula_sombra0,65,14);
11: glk_image_draw(gg_ventana2,GRAF_brujula_sombra0,65,94);
12: glk_image_draw(gg_ventana2,GRAF_brujula_sombra90,4,75);
13: glk_image_draw(gg_ventana2,GRAF_brujula_sombra90,84,75);

14: glk_image_draw(gg_ventana2,GRAF_brujula_sombra45,83,34);
15: glk_image_draw(gg_ventana2,GRAF_brujula_sombra135,25,34);
16: glk_image_draw(gg_ventana2,GRAF_brujula_sombra135,83,93);
17: glk_image_draw(gg_ventana2,GRAF_brujula_sombra45,25,91);

18: glk_image_draw(gg_ventana2,GRAF_brujula_sombracuad,162,95);
19: glk_image_draw(gg_ventana2,GRAF_brujula_sombracuad,162,132);
20: glk_image_draw(gg_ventana2,GRAF_brujula_sombracuad,162,16);
21: glk_image_draw(gg_ventana2,GRAF_brujula_sombracuad,162,55);
}!sww
}
rtrue;
];

martes, 5 de febrero de 2008

conversaciones con PSIs

Intento sacar un buen sistema de conversación con PSIs, para lo cual el sistema de lectura de objetos de Inform me resulta fatal. Lo suyo sería implementar un modo de parseado alternativo dentro del propio parser, un sistema PAWSlike de reconocimiento de cadenas de texto, de chorizos de texto no acotados necesariamente por espacios ni por palabras completas.

Mi antiguo parser en C, DISAC funcionaba así:
Tenía 4 tablas de vocabulario: Verbos, Nombres, Objetos y Adjetivos, teniendo Verbos la obligación de ser la primera cadena de la orden, y Objetos estando en principio reservada para objetos cogibles.
El parser buscaba coincidencias letra a letra sin importarle los caracteres que sobraran: signos de puntuación, espacios, sufijos, declinaciones, palabras extra... Cuando los espacios fueran importantes, sobre todo en palabras cortas que pudieran estar contenidas en otras, el espacio iba en la propia definición de vocabulario de esa palabra, con un cuadradito, tal que '#asi#'
De modo que era vocabulario válido cosas como:
'bebe', 'di#', 'por#que', 'te#llama', 'destruy', 'llave#roja', '#con#', '#en#'...

No hacía falta ni introducir las palabras completas ni una a una: las "palabras" eran a veces cachos o trozos de frase.

A la hora de buscar coincidencias, se jugaba con cuatro posibles valores de cada tabla:

1- Vocabulario concreto encontrado.
2- Indiferente de si ha habido coincidencia.
3- No debe haber habido coincidencia.
4- Debe haber habido alguna palabra de esa lista detectada, da igual la que sea.

De cara a las conversaciones, esto permitía jugar con términos y trozos de frase por los que existiera una alta probabilidad de que el jugador tuviera que pasar para formular una pregunta concreta.
Por ejemplo, para detectar una pregunta de ¿cómo te llamas?, la mayor parte de los casos con tuteo se podrían cazar definiendo dos cadenas:

'te#llama' -> Cómo te llamas, cómo te llaman.
'tu#nombre' -> cúal es tu nombre, dime tu nombre, quiero saber tu nombre.

Teniendo en cuenta los posibles errores:
-te llaman para cenar, no quiero saber cómo te llamas, tu nombre es muy feo.

Pero prefiero que se conceda el beneficio de la duda.


Volvamos a Inform, aquí existe la librería Etemas que permite jugar con UN objeto con tres modos distintos de parsing:
-parsing estricto: Deben aparecer todas las palabras de vocabulario del objeto-tema
-parsing normal: Pueden aparecer algunas, aunque falla si metes palabras no reconocidas delante.
-parsing flexible: Con que aparezca una vale, las palabras desconocidas se ignoran.

El problema es que las palabras deben estar enteras y no contener espacios, cuando ya he explicado por qué creo más útil un vocabulario definido a trozos e incluso incompleto: 'por#que' 'donde#est' ...

Descartando una inteligencia artificial capaz de reconocer las gramáticas, cosa que hace el parser pero en un argot limitado a órdenes, veo más viable basarse en trozos. Como cuando estamos rodeados de ruido y tan sólo escuchamos fragmentos de las palabras de nuestro interlocutor, y a partir de trozos clave -y del contexto- tratamos de recomponer el mensaje completo.

...

He hecho un apaño en Etemas para poder jugar con cinco listas de vocabulario independientes por objeto, y barajo incluir más de un objeto en el parsing para poder tener 5+5+5... de forma independiente.
Dos o tres sectores de vocabulario: uno para órdenes y encabezados de preguntas (averiguar si el jugador quiere saber dónde, cuándo, por qué, o si ordena que se vaya, que se quede, que vaya con él, que le dé algo); y el otro para saber el sitio, el objeto... etc.
El orden en el que se escriban dará igual, los diversos objetos se sacarán del mismo texto barriéndolo repetidamente, será lo mismo escribir: di a pepe dame la manzana, que di a pepe la manzana dame

Para eso, se añade a Etemas un cuarto modo de parsing: parsing_multiple, basado en el parsing_flexible:

[ FlexIntnombreMultiple obj u i w n;
for ( i = 0 : i < consult_words : i++ ) {
w=NextWordStopped();
if( u== 1 )if (WordInProperty (w, obj, name)) n++;
if( u== 2 )if (WordInProperty (w, obj, name_f)) n++;
if( u== 3 )if (WordInProperty (w, obj, name_mp)) n++;
if( u== 4 )if (WordInProperty (w, obj, name_fp)) n++;
if( u== 5 )if (WordInProperty (w, obj, adjectives)) n++; }
return n; ];


Ésta función será llamada con dos parámetros, el objeto obj, y un valor 'u' que indica qué lista de vocabulario de ese objeto se consultará. En una primera fase se consultarán todas y se sumarán todos los valores para descubrir el objeto-tema ganador... excepto la lista de adjetivos, cuya puntuación no será tenida en cuenta, y si aparece en la función es porque sí que será necesaria para la segunda fase. Se puede ver en AveriguarTema cómo se realiza la suma disgregada como si se tratara de un objeto-tema normal con una sola lista name.

[ AveriguarTema T i n k max tmax nn;
max=0; ! La mejor puntuación de momento
tmax=0; ! El objeto correspondiente a esta puntuación
k=wn;
objectloop (i in T) {

!...etc

if (i has parsing_multiple){
wn=consult_from; nn=0;n=0;
nn=FlexIntnombreMultiple (i,1);
n=n+nn;wn=consult_from;
nn=FlexIntnombreMultiple (i,2);
n=n+nn;wn=consult_from;
nn=FlexIntnombreMultiple (i,3);
n=n+nn;wn=consult_from;
nn=FlexIntnombreMultiple (i,4);
n=n+nn;wn=consult_from;
! nn=FlexIntnombreMultiple (i,5);
! n=n+nn; !adjectives no suma de cara a la palabra ganadora
}
!...


Y ahora la fase 2, justo antes de devolver el objeto ganador, analizamos nuevamente ese objeto, pero ésta vez dejando constancia de qué listas de ese objeto contienen vocabulario escrito por el jugador y cuales no:

!... if(tmax>0){
i=1;
wn=consult_from; !name
if(FlexIntnombreMultiple(tmax,1)>0)i=i*2;
wn=consult_from; !name_f
if(FlexIntnombreMultiple(tmax,2)>0)i=i*3;
wn=consult_from; !name_mp
if(FlexIntnombreMultiple(tmax,3)>0)i=i*5;
wn=consult_from; !name_fp
if(FlexIntnombreMultiple(tmax,4)>0)i=i*7;
wn=consult_from; !adjectives
if(FlexIntnombreMultiple(tmax,5)>0)i=i*11;
}
switch (T){
temas:temas.capciones=i;
temas2:temas2.capciones=i;
!temas3:temas3.capciones=i; !...etc
}
return(tmax); ! Y retornar el mejor hallado
! Observar que no hay intento de aclarar
! ambigüedades si dos o más coincidieran en
! puntuación
];


Usamos números primos para otorgar el valor final de la variable(propiedad) temas.capciones.
Posteriormente, podremos jugar con ese valor, por ejemplo:
Si temas.capciones vale 2, significa que sólo ha habido coincidencia en la lista de name.
Si temas.capciones vale 66, significa que ha habido coincidencias en la listas de name, name_f y adjectives.

Aquí un ejemplo de un tema, con sus valores de consulta:

xTema tSaludos "" Temas
with name 'hola' 'saludos' 'tal' 'como',
name_f 'dias' 'noches' 'tardes',
name_mp 'adios' 'voy' 'chao' 'adios' 'luego' 'vista' 'otra' 'vemos',
name_fp 'haces' 'haciendo' 'pasa' 'ocurre' 'pasado' 'ocurrido',
adjectives 'que' 'estas' 'hasta' 'buenas' 'buenos',
;
! (i==2) !hola
! (i==22) !qué tal estás
! (i==5 || i==15) !Adios
! (i==77) !¿Qué haces?
! (i==33) !Buenos días, tardes, noches


Es conveniente al menos una segunda vuelta, para detectar otro objeto de la misma forma, y poder elaborar estructuras más complejas con objetos INDEPENDIENTES. Con una sola vuelta, aunque cada objeto tenga 5 listas, éstas dependen de estar contenidas en el mismo objeto.

Ejemplo de objeto de primera vuelta:

xTema tIR "" Temas1
with name 'vete' 've' 'ponte' 'colocate' 'situate' 'subete' 'sube' 'dirigete' 'largate' 'marchate' 'metete' 'entra' 'pirate' 'sal',
name_f 'vente' 'ven' 'acompaname',
name_mp 'espera' 'esperame' 'quedate' 'aguarda',
!name_fp 'xxx',
!adjectives 'xxx',
;
!(i%2==0) !vete, ve a...
!(i%3==0) !ven, acompañame
!(i%5==0) !espera
!(i%7==0) ! sin uso aún
!(i%11==0) ! sin uso aún


Ejemplo de objeto de segunda vuelta:

xTema tsitios1 "" Temas2
with name 'rio',
name_f 'camino',
name_mp 'casa',
name_fp 'norte',
adjectives 'sur',
;
!(i%2==0) -> en algún río
!(i==6) -> en/a el camino del río
!(i==105) -> en/a el camino de la casa de pepe
!(i==35) -> en/a la casa del norte
!(i==55) -> en/a la casa del sur.
!(i%2==0) -> en algún río
!(i%35==0) -> en la casa del norte, o en el camino de la casa del norte, o al sur de la casa del norte, o en el río de la casa del norte
!...etc

Los lugares que no quepan aquí se meten en otro tema del mismo objeto Temas y listo.
Se podría crear una tercera vuelta de consulta, una cuarta... todos los objetos que se quieran a costa de velocidad de parseado, simplemente llamando repetidamente a AveriguarTemas(), con objetos contenedores de temas independientes, y previendo en el código el almacenamiento en variables diferentes del objeto ganador de cada ronda, así como sus capciones, para luego poder consultarlos en la función de conversación.

Pongamos que tenemos tres objetos contenedores de temas:
Temas1, Temas2, Temas3
Cuyos objetos ganadores se almacenan en las variable: To1, To2 y To3
Y las capciones en: Cap1, Cap2, Cap3
Podríamos tener un código de conversación tal que:

[xxxhablarSub tema i;
print "", (name)noun, " ";
print "responde: ";
to1=AveriguarTema(Temas1);
to2=AveriguarTema(Temas2);
to3=AveriguarTema(Temas3);

if(to1==tIR){!a
if(to2==tsitios1){!a1
!entiende "espera" y "casa" y "norte"
if(cap1%5==0 && cap2==35)"Me estás diciendo que espere en la casa del norte.";
}!a1
!entiende "ve" y ningún sitio de la lista, luego suponemos un ¡márchate!
if(cap1%2==0 && to2==0)"Me estás diciendo que me vaya.";
}!a
if(to2==tsitios1){ if (cap2%3==0)"Quieres saber algo referente un camino ¿eh?";}
!...etc

Otra corrección es unificar el sistema de conversación, que en Inform es disparatado, estando dividido en decir, preguntar sobre, ordenar, pedir...

Mi sistema ideal es: PERSONAJE, MENSAJE
aunque de momento lo he unificado como: DI A PERSONAJE MENSAJE

lunes, 4 de febrero de 2008

inauguración

Informitas e informitos, bienvenidos todos a la casa del compilador...
[Corten... pareces un cura, tío]
TOMA 2!

Pues nada, aquí, que me voy a hacer un blog de mis cosillas con Inform...
[¡Corten!... ahora parece el diario de una adolescente]
TOMA 3!

Hola, soy Jarel, me recordarán de aventuras como Las Llaves del Tiempo o Regreso al Edén...