Jeu interactif sur XSLT

image

Il était une fois, les gens sont venus avec le langage XML et ont vu que c'était bon. Et ils ont commencé à l'utiliser dans la mesure du possible, et même là où cela ne devrait pas. Formats pour le stockage et le transfert de données, les configurations, les services Web, les bases de données ... Il semblait regarder autour - XML, XML partout. Le temps a passé, les gens ont changé d'avis, inventé divers autres formats de données (ou caché XML dans les archives), et la folie XML a semblé se calmer. Mais depuis lors, presque tous les systèmes ont pu XML et intégrer de tels systèmes (qui a dit Apache Camel?) Est la meilleure et la plus simple utilisation des documents XML.

Là où XML est, il y a XSLT, un langage conçu pour transformer les documents XML. Ce langage est spécialisé, mais a la propriété d'être complet selon Turing . Par conséquent, le langage convient à une utilisation "anormale". Ici, par exemple, il existe une solution au problème des 8 reines . Vous pouvez donc écrire un jeu.

Pour les impatients: un programme de travail sur JSFiddle , sources sur GitHub .

Tout programme convertit l'entrée en sortie. Trois parties peuvent être distinguées dans le programme: préprocesseur, processeur et postprocesseur. Le préprocesseur prépare les données d'entrée pour un traitement ultérieur. Le processeur est engagé dans le travail principal de conversion de données, si nécessaire, "mélange" des entrées utilisateur et des signaux et événements externes, y compris dans un cycle. Un post-processeur est nécessaire pour convertir les résultats du processeur sous une forme adaptée à la perception humaine (ou par d'autres programmes).

image

Dans le cas d'un jeu interactif sur XSLT, chacune des trois parties du programme sera un fichier XSLT distinct. Le préprocesseur préparera le terrain de jeu. Le processeur appliquera le mouvement du joueur humain, fera le mouvement du joueur informatique et déterminera le gagnant. Le postprocesseur rendra l'état du jeu.

Un programme XSLT a besoin d'un runtime. Le runtime le plus courant capable d'exécuter XSLT est n'importe quel navigateur moderne . Nous utiliserons XSLT version 1.0, car il est pris en charge par les navigateurs prêts à l'emploi.

Un peu sur XSLT et XPath


XSLT est un langage de transformation de document XML; XPath est utilisé pour accéder à des parties d'un document XML. Les spécifications de ces langues sont publiées sur w3.org: XSLT version 1.0 et XPath version 1.0 .

Les bases et les exemples d'utilisation de XSLT et XPath sont facilement consultables sur le net. Ici, j'attire l'attention sur les fonctionnalités qui doivent être prises en compte lorsque vous essayez d'utiliser XSLT comme langage de programmation de haut niveau à usage général "normal".

XSLT a nommé des fonctions. Ils sont déclarés élément.

<xsl:template name="function_name"/> 

et sont appelés de cette façon:

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

Les fonctions peuvent avoir des paramètres.

Annonce:

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

Appel de fonction avec paramètres:

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

Les paramètres peuvent avoir des valeurs par défaut.

Les paramètres peuvent être «globaux» venant de l'extérieur. En utilisant ces paramètres, l'entrée utilisateur sera transférée au programme.

Le langage vous permet également de déclarer des variables pouvant être associées à une valeur. Les paramètres et les variables sont immuables et des valeurs peuvent leur être attribuées une fois (comme dans Erlang, par exemple).

XPath définit quatre types de données de base: chaîne, nombre, booléen et ensemble de nœuds. XSLT ajoute un cinquième type - un fragment d'arbre de résultat. Ce fragment ressemble à un ensemble de nœuds, mais avec lui, vous pouvez effectuer un ensemble limité d'opérations. Il peut être entièrement copié dans le document de sortie XML, mais vous ne pouvez pas accéder aux nœuds enfants.

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

La variable board contient un fragment du document XML. Mais les nœuds enfants ne sont pas accessibles. Ce code n'est pas valide:

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

Le mieux que vous puissiez obtenir est d'accéder aux nœuds de texte du fragment et de les utiliser comme une chaîne:

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

renverra "2".

Pour cette raison, dans notre jeu, le plateau (ou terrain de jeu) sera présenté comme une chaîne afin qu'il puisse être manipulé arbitrairement.

XSLT vous permet d'itérer un ensemble de nœuds à l'aide de la construction xsl: for-each. Mais la langue n'a pas les boucles for ou while habituelles. À la place, vous pouvez utiliser un appel de fonction récursif (l'itération et la récursivité sont isomorphes). Une boucle du formulaire pour x dans a..b sera organisée comme ceci:

 <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> 

Écrire un runtime


Pour que le programme fonctionne, vous avez besoin de: 3 XSLT, XML source, entrée utilisateur (paramètres), XML état interne et XML de sortie.

Nous plaçons des champs de texte avec des identificateurs dans le fichier html : "preprocessor-xslt", "processor-xslt", "postprocessor-xslt", "input-xml", "parameters", "output-xml", "postprocessed-xml". Nous plaçons également /> pour intégrer le résultat dans la page (pour la visualisation).

Ajoutez deux boutons: initialisation et appel (étape) du processeur.

Écrivons du code JavaScript.

Une caractéristique clé est l'utilisation de la transformation 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); } 


Fonctions d'exécution du préprocesseur, du processeur et du postprocesseur:
 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 fonction d'assistance parseParams () analyse l'entrée utilisateur en paires clé = valeur.

Le bouton d'initialisation appelle
 function onInit() { doPreprocessing(); doPostprocessing(); } 


Bouton de démarrage du processeur
 function onStep() { doProcessing(); doPostprocessing(); } 


Le runtime de base est prêt.

Comment l'utiliser. Insérez trois documents XSLT dans les champs appropriés. Insérer un document d'entrée XML. Appuyez sur le bouton "Init". Si nécessaire, saisissez les valeurs requises dans le champ des paramètres. Cliquez sur le bouton Étape.

Écrire un jeu


Si quelqu'un d'autre n'a pas deviné, le jeu interactif du titre est le classique 3 par 3 tic-tac-toe.

Le terrain de jeu est un tableau de 3 par 3, dont les cellules sont numérotées de 1 à 9.
Le joueur humain traverse toujours (le symbole "X"), l'ordinateur - des zéros ("O"). Si la cellule est occupée par une croix ou un zéro, le chiffre correspondant est remplacé par le symbole «X» ou «O».

L'état du jeu est contenu dans un document XML de cette forme:

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

L'élément <board /> contient le terrain de jeu; <état /> - l'état du jeu (gagner un des joueurs ou un match nul ou une erreur); l'élément <débutant /> est utilisé pour déterminer qui a commencé la partie en cours (afin qu'un autre joueur commence la partie suivante); <message /> - message pour le joueur.

Le préprocesseur génère un état initial (champ vide) à partir d'un document XML arbitraire.

Le processeur valide l'entrée utilisateur, applique son mouvement, calcule l'état du jeu, calcule et applique le mouvement de l'ordinateur.

Sur le pseudo code, cela ressemble à ceci
 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 fonction de sélection de déplacement de l'ordinateur utilise l'algorithme minimax, où l'ordinateur maximise son score et la personne le minimise. Le paramètre de profondeur de la fonction minimax est nécessaire pour sélectionner un coup qui mène à la victoire dans le moins de coups.

Cet algorithme utilise un grand nombre d'appels récursifs et le premier mouvement de l'ordinateur est calculé sur ma machine jusqu'à 2-3 secondes. Nous devons en quelque sorte accélérer. Vous pouvez simplement prendre et pré-calculer les meilleurs mouvements de l'ordinateur pour toutes les conditions de terrain de jeu acceptables possibles. De tels états se sont avérés être 886. Il est possible de réduire ce nombre en raison des rotations et des réflexions du champ, mais ce n'est pas nécessaire. La nouvelle version est rapide.

Il est temps d'afficher magnifiquement le terrain de jeu. Que faut-il utiliser si c'est quelque chose a) devrait dessiner des graphiques (21e siècle dans la cour, quel genre de jeu sans graphiques?!) Et b) il est souhaitable d'avoir un format XML? Bien sûr, SVG!

Le post-processeur dessine un champ à carreaux et y organise des croix vertes, des zéros bleus et de petites robes noires. Il affiche également des messages sur la fin du jeu.

Et maintenant, c'est comme si le jeu était prêt. Mais quelque chose ne va pas. Pour jouer, vous devez faire beaucoup d'actions inutiles, ennuyeuses et ennuyeuses: entrez le numéro de cellule dans le champ pour le code suivant et appuyez sur le bouton. Cliquez simplement sur la cellule souhaitée!

Nous finalisons le runtime et le postprocesseur .

En runtime, ajoutez la fonction de réponse en cliquant sur l'élément SVG:

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

Dans le postprocesseur, nous ajoutons un carré au-dessus de chaque cellule (la transparence est spécifiée par le style rect.btn), lorsque vous cliquez dessus, une fonction avec le numéro de cellule est appelée:

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

Une fois le lot terminé, un clic sur n'importe quelle cellule en démarre une nouvelle. Le bouton «Init» ne doit être pressé qu'une seule fois au tout début.

Vous pouvez maintenant considérer le jeu comme terminé. La chose est petite: cachez l'intérieur, emballez-le dans une application électronique, mettez-le sur Steam, ???, réveillez-vous riche et célèbre.

Conclusion


Un programmeur avisé peut tout écrire sur n'importe quoi, même en JavaScript . Mais il vaut mieux utiliser un outil adapté à chaque tâche.

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


All Articles