miércoles, 29 de abril de 2009

Gráficos con Inkscape

Llevo cosa de un mes usando Inkscape, a la par que Urbatain (lo cual habrán notado por su serie de pruebas de concepto de El Anillo).

Haciendo los gráficos para "La venganza de Yan", empecé con un estilo sencillo para poder ir rápido. El caso es que cuando vas cogiendo destreza, puedes hacerlo menos sencillo sin perder velocidad.
He usado mucho del reciclaje de siluetas para abarcar las diferentes localidades de una misma zona.
Por ejemplo, para dibujar un bosque pinto unos pocos modelos de follaje, ramas y troncos, y lo que sigue es un copy-paste a lo bestia. Una vez compuesta la escena, se copia y se reordena todo para hacer otra localidad.

Y no todo es Inkscape. Aunque es ideal para dibujar las siluetas y calcar de imágenes... para los retoques finales o para dibujar las sombras de los personajes es preferible usar un programa de retoque fotográfico, a base de máscaras.
Así lo estoy haciendo finalmente, tras una fase incial de intentar obtener el acabado definitivo con el propio Inkscape.

El nivel de calidad ha ido in-cresccendo, y eso se nota comparando los primeros dibujos con los últimos. Las primeras localidades voy a tener que redibujarlas porque se han quedado muy atrás.






El tamaño de los gráficos es de 720 x 195 px

lunes, 6 de abril de 2009

La Venganza de Yan

Estos días estoy programando una historia que se me ocurrió el jueves. La programación va a buen ritmo, sólo que me falta por planear las secuencias finales (que no el final). Realmente ha ido saliendo todo improvisado a partir de la idea general.
La ambientación está inspirada en un antiguo largometraje animado chino llamado "Taro el niño Dragón (Tatsunoko Taro)"... aunque de la inspiración a la expiración hay un trecho ;D

Por fín voy a implementar para algo más que para juegos de palabras lo que aprendí del código de "Ad Verbum": para conversaciones con PSIS y como apoyo a otros objetos del juego, para poder especificar detalles sin necesidad de crear nuevos objetos ligados.

Se trata de un parseado en paralelo, que no suplanta sino que complementa, un parseado de reconocimiento de cadenas o de trozos de texto.
Inform funciona igual que siempre, sólo que cuando quiero le consulto al otro parseado qué ha encontrado, y así puedo dar mejores respuestas a las cosas que me interesen.

He dispuesto 5 variables que recolectan los valores coincidentes del parseado en paralelo, y son estas variables las que se consultan luego desde el código de los objetos y las funciones.

Es un sistema a la vieja usanza. Te tienes que haces una tabla y apuntar qué número está representando a qué concepto (uso "concepto" para describir un grupo de palabras o cadenas que vienen a ser sinónimas en el contexto del juego). Y a la vez escoger bien cómo haces las listas, en qué orden pones las cadenas en el código y cuál de las 5 variables será la encargada de almacenar cada tipo de concepto. En principio una variable cazará conceptos referidos a pronombres, otra cazará partículas interrogativas, otra acciones, otra nombres y otra adjetivos, en principio, porque en la práctica se mezcla todo un poco para aprovechar donde no se necesita y no se prevee que haya conflicto.

La detección de cadenas se realiza por barrido secuencial, no por el orden en que ha sido escrito, sino por el orden en el que cada cadena está escrita en el código. Cada variable adquiere el valor del último concepto coincidente de su lista personal. De modo que si dos cadenas vitales están en la misma lista, la variable sólo almacenará la que esté más abajo en el código.

Así he empezado a programar y ya es tarde para cambiarlo, pero explicaré un sistema mejor:
En lugar de unas pocas variables para muchos conceptos...
Una variable para cada concepto, y claro, no usaremos variables sino Flags (banderas), que apenas usan memoria. Puedes crear 5000 flag y el parser ni pestañea.

De esta forma, antes de cada input ponemos todos los Flags encargados de pescar conceptos en false, y procedemos a hacer el barrido.

En nuestro código del PSI, tendremos cosas como éstas:
react_before[;
hablar:
print "Pepito dice:^";
if(FlagOn(100) && FlagOn(156)) "Me llamo Jhames.";
if(FlagOn(101) && FlagOn(157)) "El pescador se llama Josema.";
if(FlagOn(111) && FlagOn(157)) "¡¿Cómo que quieres matar al pescador?!.";
],


Como decía, hay que tener la chuleta al lado, y veremos las cadenas que activan cada flag:
Flag 100: llama*, nombre
Flag 111: mata*, asesina*, liquid*
Flag 156: tu, usted, te
Flag 157: pesc*d*r



(*) Como esto va por fragmentos, los asteriscos los he puesto a modo representativo para que se vean las partes que se pueden omitir sin problemas. Para palabras largas, para preveer varias flexiones... simplemente detectas las letras de la palabra que son fijas y que son imprescindibles para distinguirla de otra similar.
También se puede fijar la longitud.
Podemos detectar de un plumazo "amigo" y "amiga", programando la detección de "a"+"m"+"i"+"g+"+longitud=5.
Aunque para este ejemplo, lo dejaríamos en "a"+"m"+"i"+"g". Ya que la secuencia "amig" no presenta demasiado riesgo de confundirsem (o estar fundida dentro) con otra palabra. Sin determinar la longitud, el Flag saltaría a la que el jugador escribiera "amigo", "amiga", "amigos", "amigas", "amigable", "amigablemente"... etc

Y así funciona. Que el parser detecte que quieres que "Fulano te acompañe hasta el rio cantando una canción, con las manos en la cabeza y dando saltitos" es posible técnicamente, aunque aviso que no pienso implementar chorradas de ese estilo XD.

La máxima utilidad es distinguir intenciones y preguntas respecto a sujetos.
Puedes decir "Fulano"... ¿Fulano qué?
Dónde vive Fulano.
Quién es Fulano el de la derecha.
Quién es Fulano el de la izquierda.
...
La espada de Fulano... ¿qué pasa con la espada de fulano?
Dame la espada de Fulano
Encuentra la espada de Fulano
Fulano no tiene espada
...
Quiero matar a Fulano.

Hoygan nesesito un krak para Fulano es urjente.

Fuera de las conversaciones, esto sigue siendo muy útil. Por ejemplo, para un combate:
Tenemos un objeto "troll" y queremos poder elegir a dónde le atizamos con la espada.
Normalmente habría que crear un objeto "mano", un objeto "brazo", un objeto "pestaña"...

Con este sistema simplemente creamos el objeto "troll", así:
object Troll "Troll" limbo
with name 'troll' 'planseldon',
adjectives 'mano' 'manos' 'brazo' 'brazos' 'pierna' 'piernas' 'derecha' 'izquierda' 'pestaña',
before[;
Attack: print "Golpeas al troll en ";
if(FlagOn (50))"una mano.";
if(FlagOn (51))"un brazo.";
if(FlagOn (52))"una pierna";
!etc
"una parte al azar de su cuerpo.";

],
has animate scenery talkable;


Nuevamente tendríamos que mirar la lista para ver qué activa cada flag. Pero en este caso, del propio código se deduce:
Flag 50: mano*, dedo*
Flag 51: brazo*, codo*
Flag 52: pie/, pies/, pierna*
!etc

Resúmen de lo que hemos hecho:
  1. Un único objeto
  2. Introducimos los nombres de todos los blancos o subelementos en la propiedad 'adjectives' para que el parser trague con ellos (recordemos cómo funciona Inform, el parseado en paralelo "detectacascajos" actúa de apoyo, pero no sustituye) (*** ver NOTA1 al final)
  3. En el código del objeto consultamos las pesquisas que ha realizado el parseado en paralelo de detección de cadenas, a ver si "ha pescado algo". De ser así, podemos programar fácimente una respuesta personalizada, y si no, dejamos que salte la respuesta estandar.

*** NOTA1:
El código del Troll arriba expuesto sólo es viable-razonable en un modo de parseado tal que la propiedad adjectives no tenga la misma jerarquía que la propiedad 'name' sino que sea dependiente. ('adjectives' sólo se consulta si antes ha habido coincidencia en la propiedad 'name')
En caso contrario, el código expuesto es inviable, pues estaría abierto a disparates tales como escribir "ex pestaña" y recibir la descripción del troll".

***NOTA2:
El código para realizar este parseado "bruto" tipo PAWS en paralelo está expuesto AQUÍ, aunque en la versión "poco óptima", usando unas pocas variables en lugar de millones y millones de Flags.

jueves, 2 de abril de 2009

Nearco 3

Nearco 3 me ha gustado por su sentido del humor, consigue unos puntos muy buenos sobre todo con el uso de sonidos.
El problema de esta aventura es la rigidez de su parseado. Peor que algo programado en BASIC por reconocimiento de cadenas, ya que aquí, además de reconocer unas pocas cadenas, tu comando se va al traste si introduces algo de más que el parser no entiende o intentas expresarte de otra forma. La lista de acciones es muy pobre.
Si bien esto al principio no es gran impedimento para la jugabilidad. Tomas consciencia de las limitaciones y te atienes a ellas.
El problema gordo vino casi al final del juego, con un puzzle para el que había con interaccionar con algo que ni si quiera aparecía mencionado en la descripción de la localidad.
Eso por un lado. Por otro, el objetivo de ese puzzle era conseguir un material que estaba presente en otros puntos del juego.
Entonces, por un lado te encuentras con que no puedes obtener ese material de las otras fuentes lógicas porque el autor no ha programado esa posibilidad. Y el lugar de donde lo tienes que obtener es invisible, tienes que suponer que existe eso en esa localidad o recordar un texto de transición de una escena que no vuelve a repetirse y donde se menciona que eso existe.

Posteriormente, Jhames, el autor, defiende la interactividad de su aventura poniendo unos ejemplos que ya hacen caer rayos al nubarrón que tenía en la cabeza a raíz de lo citado anteriormente.

La interactividad no es poder examinar una gaviota, ni tener prevista una respuesta por defecto para "cantar" o para intentar coger el sol.
La interactividad implica que el jugador tiene información suficiente para interactuar y los objetos responden a los intentos de interactuación en base a sus posibilidades y cualidades.

Una red no es interactiva aunque se pueda examinar, máxime cuando necesitas un trozo de cuerda y ni se ha contemplado la posibilidad de obtenerla de ese objeto. ¿Y eso por qué? Porque el autor ha previsto que la cuerda se obtiene de otro sitio, porque sí.
También podrías necesitar un palo y, pese a estar rodeado de árboles, ramas y palos, la única solución posible sea darle un objeto a alguien para que éste te dé el palo. Perfectamente comprensible...

Tampoco es muy interactivo al día de hoy que el parser no entienda formas alternativas (nada rebuscadas) de expresar una orden, o no entienda nada en absoluto si te da por añadir un adjetivo, un complemento genitivo de más, una palabra de más (de más para la estrechez del parser).

Eso sí, reconozco que me he pasado un pelo...