miércoles, 30 de diciembre de 2020

Como se hizo. Envío de datos a un juego desde la página que lo contiene

Los juegos didácticos del proyecto AJDA son escenas de DescartesJS que se encuentran dentro de una página o documento html.

Pongamos que tenemos una página html en cuyo cuerpo están los siguientes elementos:

<body>

<div align="center">PÁGINA PADRE</div>

<input id="fecha" type="text" />  
<input id="curso" type="text" />
<input id="enviar" type="button" value="enviar curso">  
<select name="selec" id="respuesta">
      <option value="0" selected></option>
      <option value="1">A</option>
      <option value="2">B</option>
      <option value="3">C</option>
      <option value="4">D</option>
    </select>

<ajs>
Aquí iría el código del juego
</ajs>
</body>

En algunas ocasiones puede ser necesario pasar datos de variables desde el documento html al juego. El código javascript que debe insertarse en la cabecera de la página html que contiene el juego es el siguiente:

<head>
<script>

window.addEventListener("load", function(evt) 
{
    window.addEventListener("message", funcionQueManejaLosMensajes);

    var data = evt.data;

     //Envio de variable de forma automática, tras hacer cualquier acción en el navegador
     var contenido = document.getElementById("fecha");

     contenido.addEventListener("change", function(evt) 
       {
    window.postMessage({ type: "set", name: "enviofecha", value: contenido.value }, '*');
    window.postMessage({ type: "update" }, '*');
       });

   //Envio de variable al pulsar el botón enviar
   var contenido1 = document.getElementById("curso");
   var boton1 = document.getElementById("enviar");

   boton1.addEventListener("click", function(evt) 
      {
  window.postMessage({ type: "set", name: "enviocurso", value: contenido1.value }, '*');
  window.postMessage({ type: "update" }, '*');
      });

  //Envio de variable al cambiar el valor del campo selector
  var contenido2 = document.getElementById("respuesta");

  contenido2.addEventListener("change", function(evt) 
     {
 window.postMessage({ type: "set", name: "m1", value: contenido2.value }, '*');
 window.postMessage({ type: "update" }, '*');
    });

});

</script>
</head>

En juego recibirá las variables: enviofecha, enviocurso y m1, cuando se desencadenen los eventos correspondientes que disparan su envío.

Detallamos la instrucción que realiza el envío de las variables:

         window.postMessage({

           type: "set",
             name: "VARIABLE QUE RECIBIRÁ EL JUEGO",
                  value: VARIABLE DEL DOCUMENTO HTML QUE SE QUIERE PASAR AL JUEGO
      }, '*');
         window.postMessage({
             type: "update"
         }, '*');


Con este código, los datos de la VARIABLE DEL DOCUMENTO HTML QUE SE QUIERE PASAR AL JUEGO, se pasarán al éste mediante la VARIABLE QUE RECIBIRÁ EL JUEGO. El juego utilizará la VARIABLE QUE RECIBIRÁ EL JUEGO con los datos recibidos como una variable más del mismo.

El código debe ir entre etiquetas <scripts></scripts> y mediante instrucciones javascript se puede controlar cuando se ejecuta mediante la utilización de condicionales, funciones y/o eventos.

El evento window.addEventListener("load", function(evt) {...} permite que se produzca la transferencia de varuibles.

La instrucción window.postMessage({ }) puede repetirse tantas veces como variables quieran pasarse desde el documento html al juego.

Una aplicación práctica de esto es la carga en juegos del idioma o por defecto o del fichero de preguntas por defecto.

Como se hizo. Carga de idioma por defecto

Por cuestiones de seguridad informática, los navegadores no permiten la lectura directa, es decir, a través de código de programación, de ficheros alojados en el equipo local del usuario.

Los juegos didácticos del Proyecto AJDA cargan el idioma de la interfaz de ficheros de texto (hay un fichero .txt para cada idioma). Cuando el juego se ejecuta desde la web de AJDA (a través de Internet) no hay problema ya que los ficheros de idioma se cargan desde el servidor web y el navegador lo permite sin problema. Sin embargo, cuando los juegos se ejecutan directamente desde el equipo local del usuario, los navegadores no permiten la carga de los ficheros de idiomas por las mencionadas cuestiones de seguridad.

Si no se carga la interfaz, el juego no puede utilizarse directamente desde el ordenador del cliente. Para solucionar este problema, los juegos se han diseñado para que el fichero de idioma por defecto (esp.txt), se cargue de forma automática al principio del juego desde la web del Proyecto ADJA, utilizando las tecnologías AJAX, tal y como comentamos a continuación.

En la cabecera de cada juego, entre las etiquetas <head> y </head>, se introduce el siguiente código javascript que comentamos a continuación:

<script>

var nombreJuego = "Nombre del fichero que contiene el juego";
var idm;

function leerIdioma(urlIDM)
{
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function()
     {
    if ( xmlhttp.readyState == 4 && xmlhttp.status==200 )
           {  
idm = xmlhttp.responseText;
 
      window.addEventListener("load", function(evt) 
                     {
        window.postMessage({
          type: "set",
            name: "IDMDEFECTO",
              value: idm
     }, '*');
        window.postMessage({
            type: "update"
        }, '*');
      });
        }
}
xmlhttp.open("POST", urlIDM, false);
xmlhttp.send(); 
}
var urlIDM = servidor  + rutaJuegosPrefijo + nombreJuego + rutaIdioma;
leerIdioma(urlIDM);

</script>
  • Las variables: urlIDM, servidor, rutaJuegosPrefijo, nombreJuego, rutaIdioma, sirven para indicar la ruta completa en la que se encuentra el fichero de idioma por defecto dentro de la web del Proyecto AJDA.
  • La llamada a la función leerIdioma(urlIDM) se realiza después de ser definida, por lo que se ejecuta automáticamente, pasándole como parámetro la variable que contiene la ruta del fichero de idioma que se encuentra en la web de AJDA (urlIDM).
  • La leerIdioma(urlIDM) es la que se encarga de traer los datos del fichero de idioma por defecto a través de las tecnología AJAX y su funcionamiento es el siguiente:
    • Primero se crea el objeto javascript XMLHttpRequest(), que permite traer información de una URL en segundo plano (puede traer datos de otra web).
    • Después se indica que cada vez que cambie el estado de la propiedad readyState (estado de la llamada a la información pedida) por el objeto XMLHttpRequest(), a través del evento onreadystatechange, se ejecute la siguiente función (ver siguiente punto).
    • La función en primer lugar comprueba que la respuesta es completa (xmlhttp.readyState ==4) y correcta (xmlhttp.status==200). En caso de serlo se introduce en la variable idm los datos recibidos en el objeto xmlhttp en formato texto (.responseText).
    • A continuación se desencadena un evento window.addEventListener, que pasa el valor de la variable idm a la variable IDMDEFECTO y envía el valor de IDMDEFECTO a la escena del juego que está en la misma página, en su parte <body>, a través de la instrucción  window.postMessage (que tiene el formato indicado en el código).
    • Por último se indica la forma de comunicación: método (GET/POST), dirección URL y sincronicidad (true/false, false indica que se haga de forma síncrona) (xmlhttp.open("POST", urlIDM, false))a través de la cual se van a enviar los datos solicitados y se ordena su envío (xmlhttp.send()).
En la parte del juego, al comenzar este se ordena ejecutar automáticamente la función: FICHEROIDIOMA(), que es un algoritmo que realiza lo siguiente:
  • Introduce el valor de IDMDEFECTO en la variable cadena3b (cadena3b=IDMDEFECTO).
  • Crea la variable cadena3 que lo que hace es preparar la variable cadena3b para que pueda ser transformada en un vector (cadena3='<IDM>\n'+cadena3b+'</IDM>').
  • Convierte la variable cadena3 en el vector IDM (_StrToVector_(cadena3,'IDM')).
A partir de este momento, los valores del vector IDM (IDM[0], IDM[1], IDM[2]... ), pueden ser usados en los nombres de los controles y textos de la interfaz del juego.

Como se hizo. Fichero de resultados

Otra opción que presentan los juegos del Proyecto AJDA consiste en la posibilidad de guardar, en cualquier momento de la partida, los resultados que los jugadores están obteniendo en la misma a través del botón resultados, situado en la parte superior izquierda.

Al ser pulsado se podrá guardar un fichero de texto, con el nombre deseado, que contendrá los datos más significativos que cada jugador lleva en el momento del juego en el que es generado.

Desde el punto de vista de la programación de juego, cuando se acciona el citado control se desencadenan las siguientes acciones:

  • Se introducen los datos significativos de la situación de la partida en las diferentes filas de un vector denominado RS1 (RS1[0]=var0; RS1[1]=var1; RS1[2]=var2;...).
  • Se ejecuta la función algorítmica guardar(), que pasa todos los valores del vector RS1 a la variable texto de la siguiente forma:
inicio='texto=''; ter=0; maximo=nº lineas necesarias' 
hacer='texto=(ter<maximo-1)?(texto+RS1[ter]+'\n'):texto+RS1[ter]; ter=ter+1' 
mientras='ter<maximo'

  • La instrucción _Save_('resultado.txt',texto), guarda los datos de la variable texto en un fichero de texto, cuyo nombre por defecto es resultado.txt.

Se debe tener en cuenta lo siguiente:
  • Se pueden guardar tantos ficheros de resultados como se deseen y conviene indicar en el nombre del fichero una referencia de cuando fue generado.
  • El fichero de resultados se genera en el idioma que esté definida la interfaz en el momento de guardarse.
  • No se debe confundir el fichero de resultados con el de guardar partidas para después continuarlas posteriormente. El fichero de resultados es legible y editable y no sirve para reanudar partidas.
  • Los datos incluidos en el fichero de resultados son característicos de cada juego.
  • La finalidad de este tipo de fichero es dejar un registro de los resultados de la actividad en cualquier momento.

martes, 29 de diciembre de 2020

Como se hizo. Juegos con tiempo

En parte de los juegos de AJDA es necesario utilizar y medir el tiempo de duración de las preguntas u otras acciones del juego.

El tiempo es un valor que se introduce en una o varias variables y cuya variación se controla normalmente a través del algoritmo de la pestaña Animación del editor de DescartesJS.


A través del mismo se inicializan las variables y se establece cuando empieza, se para, reanuda, etc, a través de las instrucciones incluidas en sus apartados hacer y mientras. A través del mismo se pueden introducir varias variables para controlar más de un tiempo, por ejemplo, para controlar un tiempo para cada jugador.

Sin embargo, es importante tener en cuenta que la ejecución del algoritmo puede ser más o menos rápida dependiendo del equipo informático en el que se ruede. Además, en muchas ocasiones puede ser conveniente poder modular el transcurso del mismo.

Por ello, los juegos que utilizan tiempo, llevan un control tipo barra para modulación de la velocidad del mismo en la parte superior derecha. Este control, llamado habitualmente TIM, puede ser modificado en cualquier momento del juego y cuyo valor se incluyen en los incrementos y decrementos de tiempo de los juegos.



Otra cuestión relacionada con el tiempo es que cuando los cronómetros se presentan en pantalla y se quiere que el valor del tiempo aparezca centrado en ellos, al pasar de 3 cifras, a 2 o a 1, la posición del valor del tiempo del cronómetro se descentra. Actualmente DescartesJS contiene funciones que permiten centrar los textos, pero cuando la mayoría de los juegos fueron realizados dichas instrucciones no estaban implementadas. Para ello se utilizaron condicionales que desplazaban la posición de los textos según su número de cifras:

 [20+((t>=10)?5),30]

Otra opción más reciente fue utilizar controles tipo botón, que no ejecutasen ninguna acción para utiliza cronómetros y marcadores con valores centrados.

Como se hizo. Efectos sonoros

Los juegos didácticos del Proyecto AJDA cuentan con efectos sonoros, que pueden activarse o desactivarse en cualquier momento del juego a través del correspondiente control situado en la parte superior derecha:


Al ser pulsado este control el valor de la variable MUS cambia de 0 a 1 y viceversa a través del siguiente cálculo:

MUS=(MUS=0)?1:0

Cuando MUS=0, la música está activada y cuando MUS=1 está desactivada. La imagen que se muestra en el control de sonido depende del valor de la variable MUS.

[(MUS=0)?'sonido-on.png':'sonido-off.png']

Cada uno de los efectos sonoros de los juegos se encuentra en un fichero con extensión .mp3 y estos se activan normalmente a través de eventos que se de forma alterna cuando se cumplen una determinadas condiciones establecidas por la dinámica del juego.

evento='si;' condición='(condiciones) acción='reproducir' parámetro='agua.mp3'

Cuando el efecto sonoro es de mayor duración y este se quiere detener cuando se cumpla una determinada condición, se incluye la palabra stop tras el nombre del fichero en el evento correspondiente.

evento='si;' condición='(condiciones) acción='reproducir' parámetro='agua.mp3 stop'

El fichero de texto que contiene el efecto sonoro se encuentra el el mismo directorio o carpeta del juego. Si un fichero de sonido se sustituye por otro con el mismo nombre, el sonido presentado sería el del nuevo fichero.

Actualmente DescartesJS tiene la posibilidad de incluir reproductores de audio y vídeo, con panel de controles de reproducción y parámetros específicos que permiten el control de las mismas mediante instrucciones de Descartes.

Como se hizo. Continuación de partidas

Los juegos didácticos del Proyecto AJDA permiten guardar las partidas y continuarlas en otro momento. Comentamos los principales aspectos de la implementación de este característica en los juegos.

Los juegos están preparados para poder ser guardados en determinados momentos, esto se refleja en que el control "Guardar partida", situado en la parte superior centro, esté o no activado:


Cuando se acciona este control los datos significativos del juego se almacenan en un vector denominado GP1 (a cada linea del vector se la da un valor de un dato del juego que debe ser guardado, GP1[0]=var0; GP1[1]=var1; GP1[2]=var2;...), después  se aplican las instrucciones: guardarpartida() y _Save_('NOMBRE-FICHERO.txt',partida).

El algorítmo guardarpartida(), pasa todos los valores del vector GP1 a la variable partida de la siguiente forma:

inicio='partida=''; conter=0; maximopar=nº lineas necesarias' 

hacer='partida=(conter<maximopar-1)?(partida+GP1[conter]+'\n'):partida+GP1[conter]; conter=conter+1' 

mientras='conter<maximopar'

La instrucción _Save_('NOMBRE-FICHERO.txt',partida), guarda los datos de la variable partida en un fichero de texto, cuyo nombre por defecto es NOMBRE-FICHERO.txt.

Por tanto, tenemos guardados en un fichero de texto los datos que permiten continuar una partida y podemos guadar tantos ficheros como momentos de la partida queramos poder continuar.

Para continuar una partidas guardada, al comienzo del juego se presenta el botón "Continuar partida".


Al pulsar sobre se abrirá el explorador de archivos del navegador y podremos seleccionar un fichero con los datos guardados de una partida del juego que queramos continuar, utilizando la instrucción _Open_('abrirficheropar'), que además ejecuta el algoritmo abrirficheropar(), que en su campo hacer ejecuta las siguientes acciones:
  • Crear la variable cadenapar1, introduciendo <w1> y </w1> como primera y última línea y entre ellas el contenido del fichero que se acaba de cargar que se encuentra en DJS.fileContent.
cadenapar1='<w1>\n '+DJS.fileContent+'</w1>';

  • Pasa el contenido de la variable cadenapar1 al vector w1 mediante la  instrucción:

_StrToVector_(cadenapar1,'w1';)

  • Se establece una variable  PCAR que si su valor es 1, indica que se ha cargado correctamente.
  • Se asignan los datos del vector wp1 a las variables del juego que les corresponden,

jug1=(PCAR=1)?w1[2]:jug1 ; jug2=(PCAR=1)?w1[3]:jug2 ;  TIME=(PCAR=1)?w1[4]:TIME... 

El juego continuará ahora con los datos de la partida que se guardó. 

Como se hizo. Preguntas en ficheros de texto

La mayoría de los juegos didácticos del Proyecto AJDA permiten generar y almacenar las preguntas que se utilizarán en los juegos en ficheros de  texto, los cuales se cargan en los juegos cuando se desean utilizar. En este artículo vamos a analizar como se ha realizado ésto desde el punto de vista de la programación de DescartesJS.

Antes de empezar el juego (o durante el transcurso del mismo, si es necesario) se carga un fichero de texto con las preguntas y sus datos son introducidos en una variable llamada TOTALENVI. La pantalla de carga del fichero se muestra a continuación. Se trata de un iframe, programado en lenguaje javascript, e insertado en la escena de Descartes del juego. Esta parte la trataremos de forma específica en otro artículo.

Al iniciarse el juego se ejecuta automáticamente el algoritmo: FICHERO-PREGUNTAS, que en su campo hacer ejecuta las siguientes instrucciones:

cadena1b=TOTALENVI

cadena1='<VEC>\n '+cadena1b+'</VEC>'

SUBIRFICH=(CFI=0)?_StrToVector_(cadena1,'VEC'):' '

  • En la variable cadena1b se cargan los datos de la variable TOTALENVI que ha sido enviada al juego en el momento de su carga y que contiene los datos del fichero de preguntas que se cargarán en el juego.
  • La variable cadena 1, es la variable cadena1b a la que se la incluido al principio <VEC> (y un salto de línea \n) y al final </VEC>. Esto es necesario para que en la siguiente operación la cadena 3 pueda ser transformada en un vector.
  • La función SUBIRFICH convierte la cadena 1 en el vector VEC.
Ahora las preguntas y respuestas del fichero se encuentran en el juego en las distintas líneas del vector VEC (cada línea del fichero es una línea del enunciado, una opción de respuesta, una solución...), las cuales pueden ser incluidas en los textos, las soluciones dadas por los jugadores comparadas con las correctas, las preguntas pueden ser ordenadas aleatoriamente mediante algorítmos, etc.

Si durante el juego se necesita cargar otro fichero de preguntas, lo que se hace es actualizar de nuevo los valores de la variable TOTALENVI. Si se necesitan en un juego cargar varios ficheros de preguntas, se generan varias variables TOTALENVI1, TOTALENVI2, TOTALENVI3... y cada una de ellas se pasa  su correspondiente vector: VEC1, VEC2, VEC3,,,

Como se hizo. Interfaz multi-idioma

Una de las características de los juegos didácticos es que se puede seleccionar el idioma de la interfaz, en cualquier momento, a través del menú desplegable situado en la esquina superior derecha de todos los juegos.

A continuación se comentan los  principales aspectos de su implementación, teniendo en cuenta que las palabras o frases de cada idioma se introducen en un fichero de texto, manteniendo el mismo orden en todos ellos.

El citado menú de control , identificado como FICHIDM, contiene una opción para cada idioma. Al seleccionarse un determinado idioma a través del mismo se realizan las siguientes acciones:

  • Dar un valor a la variable FICHID, que adquirirá el dato del nombre del fichero de texto del idioma en cuestión (por defecto el idioma es español, esp, establecido como tal en las variables iniciales por defecto):

FICHID=(FICHIDM=0)?'esp':((FICHIDM=1)?'alb':((FICHIDM=2)?'ale':((FICHIDM=3)?'ara':((FICHIDM=4)?'ben':((FICHIDM=5)?'bul':.....:'esp')))))...

  • La última opción del menú permite cargar directamente el fichero de idioma a través del explorador del navegador, lo cuál permitiría utilizar ficheros de idioma diferentes a los existentes para el juego. La instrucción que se usa en este caso es:
cargaridm=((FICHIDM=43)?_Open_('abriridioma'):0)

  •  Se establece la variable que indica la ruta relativa completa del fichero de idioma TOTALID (donde RUTAID es la ruta relativa y EXT la extensión ".txt"):

TOTALID=RUTAID+FICHID+EXT

  • Se ejecuta la definición/algoritmo: FICHEROIDIOMA() 
El algorítrmo FICHEROIDIOMA(), en su campo hacer contiene las siguientes instrucciones, que se comentan a continuación:

cadena3b=(_Load_(TOTALID)#'')?_Load_(TOTALID):IDMDEFECTO
cadena3='<IDM>\n '+cadena3b+'</IDM>'
SUBIRIDM=(CID=0)?_StrToVector_(cadena3,'IDM'):' '

  • En la variable cadena3b se cargan los datos del fichero de idioma cuya ruta viene dada por la variable TOTALID (se hace una comprobación de que el fichero existe y si no carga el idioma por defecto).
  • La variable cadena 3, es la variable cadena3b a la que se la incluido al principio <IDM> (y un salto de línea \n) y al final </IDM>. Esto es necesario para que en la siguiente operación la cadena 3 pueda ser transformada en un vector.
  • La función SUBIRIDM convierte la cadena 3 en el vector IDM.
Ahora ya cada nombre de control o etiqueta de texto del juego debe incluir como nombre o texto a mostrar el dato del vector del fichero de idioma correspondiente. Para los nombre de controles debe ir entre corchetes [IDM[nº líne del texto]] y para los textos se introducen como variables, con el valor IDM[nº líne del texto].

lunes, 12 de octubre de 2020

Como se hizo. Números aleatorios diferentes en juegos

En este artículo presentamos como obtener varios números aleatorios diferentes para los juegos del Proyecto AJDA desde el punto de vista de su código DescartesJS. Esta opción es muy utilizada en los juegos, como por ejemplo para: que las preguntas que se hacen a los participantes sean diferentes y aleatorias, obtener diferentes cartas de una baraja, obtener diferentes fichas de dominó, seleccionar diferentes participantes para realizar una acción en un suelo, etc.

En primer lugar se crea un vector vacío, cuyas dimensión sea la del número aleatorio máximo que se puede obtener (se puede indicar un número o dejarlo como parámetro). En este ejemplo llamaremos al vector valores 2.


Se crea un algoritmo (que hemos llamado iniciaValores2()) que rellena cada una de las filas del vector creado anteriormente desde cero hasta la cifra máxima del número aleatorio, tal y como se indica a continuación.


Se genera un nuevo algoritmo (que hemos denominado permutar2()) que asigna a cada fila un valor entre el cero y la cifra máxima pero con una reordenación aleatoria. Comentamos el funcionamiento del algoritmo: se van a ir recorriendo las distintas filas del vector (valores2) por orden secuencial (desde cero a la Cifra-max) y en cada ciclo primero se obtiene un numero aleatorio entre cero y la cifra máxima, después se extrae el valor de la fila del vector dado por ese número aleatorio, a la fila extraída aleatoriamente se le da el valor de la fila que le corresponde por orden secuencial de la iteración y a ésta el valor de la fila sacada aleatoriamente. De esta forma en cada iteración se van permutando los números de las filas del vector de forma que tenemos un vector con un número aleatorio diferente en cada fila.


Cuando invoquemos los dos algoritmos creados (en el ejemplo iniciaValores2() y permutar2()) tendremos un vector (valores2) cuyas filas contienen números diferentes y con valores aleatorios entre el cero y la cifra máxima que podremos utilizar cuando necesitemos.

Como se hizo. Números aleatorios en juegos

En los juegos del Proyecto AJDA se utilizan muy frecuentemente los números aleatorios. Ya sea al tirar un dado, una moneda, sacar cartas, colocar casillas en un panel, etc. Vamos a analizar como se ha realizado desde el punto de vista del código de DescartesJS.

Cuando simplemente se quiere obtener un número entero aleatorio entre uno y una cifra máxima, se puede invocar la siguiente función:

Número aleatorio = ent(rnd*cifra máxima)+1

Un ejemplo concreto, la función a invocar para obtener una tirada de un dado de seis caras sería la siguiente:

DADO = ent(rnd*6)+1

Otro ejemplo, la función a llamar para sacar una carta de ka baraja española podría ser:

CARTA = ent(rnd*40)+1

La cifra máxima puede dejarse como parámetro o variable,de forma que pueda ser introducida a través de controles o determinada a través de un cálculo concreto. Un caso concreto podría ser determinar aleatoriamente el jugador que empieza una partida. Teniendo en cuenta que el número de jugadores es una variable que se introduce mediante un control (por ejemplo, NJ), la función quedaría de la siguiente forma:

EMPIEZA = ent(rnd*NJ)+1

En el caso de que se quieran obtener varios números aleatorios de forma independiente, es suficiente con invocar tantas funciones de este tipo como necesitemos. Por ejemplo, si lanzamos tres dados simultáneamente, las funciones podrían ser:

DADO1 = ent(rnd*6)+1

DADO2 = ent(rnd*6)+1

DADO3 = ent(rnd*6)+1

Otra cuestión sería obtener varios números aleatorios diferentes entre si, la cual la dejaremos para otro artículo.