Juego interactivo en XSLT

imagen

Érase una vez, a la gente se le ocurrió el lenguaje XML y vio que era bueno. Y comenzaron a usarlo siempre que sea posible, e incluso donde no debería. Formatos para el almacenamiento y transferencia de datos, configuraciones, servicios web, bases de datos ... Parecía mirar a su alrededor: XML, XML en todas partes. Pasó el tiempo, las personas cambiaron de opinión, inventaron varios otros formatos de datos (u ocultaron XML dentro de los archivos), y la locura XML pareció calmarse. Pero desde entonces, casi cualquier sistema ha podido XML e integrar dichos sistemas (¿quién dijo Apache Camel?) Es la mejor y más fácil forma de usar documentos XML.

Donde está XML, allí está XSLT, un lenguaje diseñado para transformar documentos XML. Este lenguaje es especializado, pero tiene la propiedad de integridad según Turing . Por lo tanto, el lenguaje es adecuado para el uso "anormal". Aquí, por ejemplo, hay una solución al problema de 8 reinas . Entonces, puedes escribir un juego.

Para los impacientes: un programa de trabajo en JSFiddle , fuentes en GitHub .

Cualquier programa convierte entrada a salida. Se pueden distinguir tres partes en el programa: preprocesador, procesador y postprocesador. El preprocesador prepara los datos de entrada para su posterior procesamiento. El procesador participa en el trabajo principal de conversión de datos, si es necesario, "mezcla" la entrada del usuario y las señales y eventos externos, incluso en un ciclo. Se necesita un postprocesador para convertir los resultados del procesador en una forma adecuada para la percepción humana (o por otros programas).

imagen

En el caso de un juego interactivo en XSLT, cada una de las tres partes del programa será un archivo XSLT separado. El preprocesador preparará el campo de juego. El procesador aplicará el movimiento del jugador humano, hará el movimiento del jugador de la computadora y determinará el ganador. El postprocesador representará el estado del juego.

Un programa XSLT necesita un tiempo de ejecución. El tiempo de ejecución más común capaz de ejecutar XSLT es cualquier navegador moderno . Usaremos XSLT versión 1.0, ya que es compatible con los navegadores listos para usar.

Un poco sobre XSLT y XPath


XSLT es un lenguaje de transformación de documentos XML; XPath se usa para acceder a partes de un documento XML. Las especificaciones para estos idiomas se publican en w3.org: XSLT Versión 1.0 y XPath Versión 1.0 .

Los ejemplos básicos y de uso de XSLT y XPath se pueden buscar fácilmente en la red. Aquí llamo la atención sobre las características que deben tenerse en cuenta al intentar usar XSLT como un lenguaje de programación de alto nivel "normal" de propósito general.

XSLT ha nombrado funciones. Se declaran un elemento.

<xsl:template name="function_name"/> 

y se llaman así:

 <xsl:call-template name="function_name"/> 

Las funciones pueden tener parámetros.

Anuncio:

 <xsl:template name="add"> <xsl:param name="x"/> <xsl:param name="y"/> <xsl:value-of select="$x + $y"/> </xsl:template> 

Llamada de función con parámetros:

 <xsl:call-template name="add"> <xsl:with-param name="x" select="1"/> <xsl:with-param name="y" select="2"/> </xsl:call-template> 

Los parámetros pueden tener valores predeterminados.

Los parámetros pueden ser "globales" provenientes del exterior. Usando estos parámetros, la entrada del usuario será transferida al programa.

El lenguaje también le permite declarar variables que pueden estar asociadas con un valor. Los parámetros y las variables son inmutables y se les pueden asignar valores una vez (como en Erlang, por ejemplo).

XPath define cuatro tipos de datos básicos: cadena, número, booleano y conjunto de nodos. XSLT agrega un quinto tipo: un fragmento de árbol de resultados. Este fragmento se parece a un conjunto de nodos, pero con él puede realizar un conjunto limitado de operaciones. Se puede copiar completamente al documento de salida XML, pero no puede acceder a los nodos secundarios.

 <xsl:variable name="board"> <cell>1</cell> <cell>2</cell> <cell>3</cell> <cell>4</cell> </xsl:variable> 

La variable de placa contiene un fragmento del documento XML. Pero no se puede acceder a los nodos secundarios. Este código no es válido:

 <xsl:for-each select="$board/cell"/> 

Lo mejor que puede obtener es acceder a los nodos de texto del fragmento y trabajar con ellos como una cadena:

 <xsl:value-of select="substring(string($board), 2, 1)"/> 

devolverá "2".

Debido a esto, en nuestro juego, el tablero (o campo de juego) se presentará como una cadena para que pueda ser manipulado arbitrariamente.

XSLT le permite iterar un conjunto de nodos utilizando la construcción xsl: for-each. Pero el lenguaje no tiene los bucles habituales para o while. En su lugar, puede usar una llamada de función recursiva (la iteración y la recursión son isomorfas). Un bucle de la forma para x en a..b se organizará así:

 <xsl:call-template name="for_loop"> <xsl:with-param name="x" select="$a"/> <xsl:with-param name="to" select="$b"/> </xsl:call-template> <xsl:template name="for_loop"> <xsl:param name="x"/> <xsl:param name="to"/> <xsl:if test="$x < $to"> <!--  -  --> <xsl:call-template name="for_loop"> <xsl:with-param name="x" select="$x + 1"/> <xsl:with-param name="to" select="$to"/> </xsl:call-template> </xsl:if> </xsl:template> 

Escribir un tiempo de ejecución


Para que el programa funcione, necesita: 3 XSLT, XML de origen, entrada de usuario (parámetros), XML de estado interno y XML de salida.

Colocamos campos de texto con identificadores en el archivo html : "preprocessor-xslt", "procesador-xslt", "postprocessor-xslt", "input-xml", "parámetros", "output-xml", "postprocessed-xml". También colocamos /> para incrustar el resultado en la página (para visualización).

Agregue dos botones: inicialización y llamada (paso) del procesador.

Escribamos un código JavaScript.

Una característica clave es el uso de la transformación XSLT.
 function transform(xslt, xml, params) { var processor = new XSLTProcessor(); var parser = new DOMParser(); var xsltDom = parser.parseFromString(xslt, "application/xml"); // TODO: check errors .documentElement.nodeName == "parsererror" var xmlDom = parser.parseFromString(xml, "application/xml"); processor.importStylesheet(xsltDom); if (typeof params !== 'undefined') { params.forEach(function(value, key) { processor.setParameter("", key, value); }); } var result = processor.transformToDocument(xmlDom); var serializer = new XMLSerializer(); return serializer.serializeToString(result); } 


Funciones para ejecutar el preprocesador, procesador y postprocesador:
 function doPreprocessing() { var xslt = document.getElementById("preprocessor-xslt").value; var xml = document.getElementById("input-xml").value; var result = transform(xslt, xml); document.getElementById("output-xml").value = result; } function doProcessing() { var params = parseParams(document.getElementById("parameters").value); var xslt = document.getElementById("processor-xslt").value; var xml = document.getElementById("output-xml").value; var result = transform(xslt, xml, params); document.getElementById("output-xml").value = result; } function doPostprocessing() { var xslt = document.getElementById("postprocessor-xslt").value; var xml = document.getElementById("output-xml").value; var result = transform(xslt, xml); document.getElementById("postprocessed-xml").value = result; document.getElementById("output").innerHTML = result; } 


La función auxiliar parseParams () analiza la entrada del usuario en pares clave = valor.

El botón de inicialización llama
 function onInit() { doPreprocessing(); doPostprocessing(); } 


Botón de inicio del procesador
 function onStep() { doProcessing(); doPostprocessing(); } 


El tiempo de ejecución básico está listo.

Cómo usarlo Inserte tres documentos XSLT en los campos apropiados. Insertar documento de entrada XML. Presione el botón "Init". Si es necesario, ingrese los valores requeridos en el campo de parámetro. Haz clic en el botón Paso.

Escribiendo un juego


Si alguien más no lo adivinó, el juego interactivo del título es el clásico 3 por 3 tic-tac-toe.

El campo de juego es una mesa de 3 por 3, cuyas celdas están numeradas del 1 al 9.
El jugador humano siempre cruza (el símbolo "X"), la computadora - pone a cero ("O"). Si la celda está ocupada por una cruz o un cero, el dígito correspondiente se reemplaza por el símbolo "X" u "O".

El estado del juego está contenido en un documento XML de esta forma:

 <game> <board>123456789</board> <state></state> <beginner></beginner> <message></message> </game> 

El elemento <tablero /> contiene el campo de juego; <estado /> - el estado del juego (ganar uno de los jugadores o un empate o error); el elemento <principiante /> se usa para determinar quién comenzó el juego actual (para que otro jugador comience el próximo juego); <mensaje /> - mensaje para el jugador.

El preprocesador genera un estado inicial (campo vacío) a partir de un documento XML arbitrario.

El procesador valida la entrada del usuario, aplica su movimiento, calcula el estado del juego, calcula y aplica el movimiento de la computadora.

En pseudocódigo, se parece a esto
 fn do_move() { let board_after_human_move = apply_move(board, "X", param) let state_after_human_move = get_board_state(board_after_human_move) if state_after_human_move = "" { let board_after_computer_move = make_computer_move(board_after_human_move) let state_after_computer_move = get_board_state(board_after_computer_move) return (board_after_computer_move, state_after_computer_move) } else { return (board_after_human_move, state_after_human_move) } } fn apply_move(board, player, index) { //     board    index   player     } fn get_board_state(board) { //   "X",   , "O",   , "tie"          } fn make_computer_move(board) { let position = get_the_best_move(board) return apply_move(board, "O", position) } fn get_the_best_move(board) { return get_the_best_move_loop(board, 1, 1, -1000) } fn get_the_best_move_loop(board, index, position, score) { if index > 9 { return position } else if cell_is_free(board, index) { let new_board = apply_move(board, "O", index) let new_score = minimax(new_board, "X", 0) if score < new_score { return get_the_best_move_loop(board, index + 1, index, new_score) } else { return get_the_best_move_loop(board, index + 1, position, score) } } else { return get_the_best_move_loop(board, index + 1, position, score) } } fn cell_is_free(board, index) { //   true,    board   index   ( ) } fn minimax(board, player, depth) { let state = get_board_state(board) if state = "X" { //   return -10 + depth } else if state = "O" { //   return 10 - depth } else if state = "tie" { //  return 0 } else { let score = if player = "X" { 1000 } else { -1000 } return minimax_loop(board, player, depth, 1, score) } } fn minimax_loop(board, player, depth, index, score) { if index > 9 { return score } else if cell_is_free(board, index) { //   ,    let new_board = apply_move(board, player, index) let new_score = minimax(new_board, switch_player(player), depth + 1) let the_best_score = if player = "X" { //    if new_score < score { new_score } else { score } } else { //    if new_score > score { new_score } else { score } } return minimax_loop(board, player, depth, index + 1, the_best_score) } else { //      return minimax_loop(board, player, depth, index + 1, score) } } fn switch_player(player) { //   ; X -> O, O -> X } 


La función de selección de movimiento de la computadora utiliza el algoritmo minimax, donde la computadora maximiza su puntaje y la persona lo minimiza. El parámetro de profundidad de la función minimax es necesario para seleccionar un movimiento que conduzca a la victoria en el menor número de movimientos.

Este algoritmo utiliza una gran cantidad de llamadas recursivas y el primer movimiento de la computadora se calcula en mi máquina hasta 2-3 segundos. De alguna manera debemos acelerar. Simplemente puede realizar y calcular previamente los mejores movimientos de la computadora para todas las condiciones de campo de juego aceptables posibles. Dichos estados resultaron ser 886. Es posible reducir este número debido a rotaciones y reflejos del campo, pero no es necesario. La nueva versión es rápida.

Es hora de mostrar bellamente el campo de juego. ¿Qué usar si esto es algo a) debería dibujar gráficos (siglo XXI en el patio, ¿qué tipo de juego sin gráficos?!) Yb) es deseable tener un formato XML? Por supuesto SVG!

El postprocesador dibuja un campo a cuadros y organiza cruces verdes, ceros azules y pequeños vestidos negros en él . También muestra mensajes sobre el final del juego.

Y ahora el juego parece estar listo. Pero algo no está bien. Para jugar, debe realizar muchas acciones innecesarias, aburridas y molestas: ingrese el número de celda en el campo para el siguiente código y presione el botón. Simplemente haga clic en la celda deseada!

Estamos finalizando el tiempo de ejecución y el postprocesador .

En tiempo de ejecución, agregue la función de respuesta de hacer clic en el elemento SVG:

 function onSvgClick(arg) { document.getElementById("parameters").value = arg; onStep(); } 

En el postprocesador agregamos un cuadrado sobre cada celda (la transparencia se especifica mediante el estilo rect.btn), cuando se hace clic, se llama a una función con el número de celda:

 <rect class="btn" x="-23" y="-23" width="45" height="45" onclick="onSvgClick({$index})"/> 

Después de completar el lote, al hacer clic en cualquier celda se inicia una nueva. El botón "Init" solo debe presionarse una vez al principio.

Ahora puedes considerar el juego terminado. La cosa es pequeña: esconde el interior, empaquétalo en una aplicación de electrones, ponlo en Steam, ???, despierta rico y famoso.

Conclusión


Un programador de mente fuerte puede escribir cualquier cosa sobre cualquier cosa, incluso en JavaScript . Pero es mejor usar la herramienta adecuada para cada tarea.

Source: https://habr.com/ru/post/441478/


All Articles