Interaktives Spiel auf XSLT

Bild

Es war einmal, als die Leute auf die XML-Sprache kamen und sahen, dass sie gut war. Und sie fingen an, es zu benutzen, wo immer es möglich war, und sogar dort, wo es nicht sein sollte. Formate für Datenspeicherung und -übertragung, Konfigurationen, Webdienste, Datenbanken ... Es schien sich umzusehen - XML, XML überall. Die Zeit verging, die Leute änderten ihre Meinung, erfanden verschiedene andere Datenformate (oder versteckten XML in Archiven) und der XML-Wahnsinn schien sich zu beruhigen. Aber seitdem ist fast jedes System in der Lage, solche Systeme zu XML und zu integrieren (wer hat Apache Camel gesagt?). Ist der beste und einfachste Weg, XML-Dokumente zu verwenden.

Wo XML ist, gibt es XSLT, eine Sprache, die zum Transformieren von XML-Dokumenten entwickelt wurde. Diese Sprache ist spezialisiert, hat aber laut Turing die Eigenschaft der Vollständigkeit . Daher ist die Sprache für "abnormale" Verwendung geeignet. Hier gibt es zum Beispiel eine Lösung für das Problem der 8 Königinnen . Sie können also ein Spiel schreiben.

Für die Ungeduldigen: ein Arbeitsprogramm auf JSFiddle , Quellen auf GitHub .

Jedes Programm wandelt Eingabe in Ausgabe um. Im Programm können drei Teile unterschieden werden: Präprozessor, Prozessor und Postprozessor. Der Präprozessor bereitet die Eingabedaten für die weitere Verarbeitung vor. Der Prozessor ist mit der Hauptarbeit der Datenkonvertierung beschäftigt, falls erforderlich, indem Benutzereingaben und externe Signale und Ereignisse "gemischt" werden, einschließlich in einem Zyklus. Ein Postprozessor wird benötigt, um die Ergebnisse des Prozessors in eine für die menschliche Wahrnehmung geeignete Form (oder durch andere Programme) umzuwandeln.

Bild

Bei einem interaktiven Spiel auf XSLT ist jeder der drei Teile des Programms eine separate XSLT-Datei. Der Präprozessor bereitet das Spielfeld vor. Der Prozessor wendet den Zug des menschlichen Spielers an, macht den Zug des Computerspielers und bestimmt den Gewinner. Der Postprozessor gibt den Status des Spiels wieder.

Ein XSLT-Programm benötigt eine Laufzeit. Die häufigste Laufzeit, mit der XSLT ausgeführt werden kann, ist ein moderner Browser . Wir werden XSLT Version 1.0 verwenden, da es von Browsern sofort unterstützt wird.

Ein bisschen über XSLT und XPath


XSLT ist eine XML-Dokumenttransformationssprache. XPath wird verwendet, um auf Teile eines XML-Dokuments zuzugreifen. Die Spezifikationen für diese Sprachen werden auf w3.org veröffentlicht: XSLT Version 1.0 und XPath Version 1.0 .

Die Grundlagen und Verwendungsbeispiele von XSLT und XPath können einfach im Internet gesucht werden. Hier mache ich auf die Funktionen aufmerksam, die bei der Verwendung von XSLT als "normale" Allzweck-Programmiersprache auf hoher Ebene berücksichtigt werden müssen.

XSLT hat Funktionen benannt. Sie werden als Element deklariert.

<xsl:template name="function_name"/> 

und heißen so:

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

Funktionen können Parameter haben.

Ankündigung:

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

Funktionsaufruf mit Parametern:

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

Parameter können Standardwerte haben.

Parameter können von außen "global" sein. Mit diesen Parametern werden Benutzereingaben an das Programm übertragen.

In der Sprache können Sie auch Variablen deklarieren, die einem Wert zugeordnet sein können. Parameter und Variablen sind unveränderlich und können ihnen einmal zugewiesen werden (wie zum Beispiel in Erlang).

XPath definiert vier grundlegende Datentypen: Zeichenfolge, Nummer, Boolescher Wert und Knotensatz. XSLT fügt einen fünften Typ hinzu - ein Ergebnisbaumfragment. Dieses Fragment sieht aus wie ein Knotensatz, aber damit können Sie einen begrenzten Satz von Operationen ausführen. Es kann als Ganzes in das XML-Ausgabedokument kopiert werden, Sie können jedoch nicht auf die untergeordneten Knoten zugreifen.

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

Die Board-Variable enthält ein Fragment des XML-Dokuments. Auf die untergeordneten Knoten kann jedoch nicht zugegriffen werden. Dieser Code ist ungültig:

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

Das Beste, was Sie bekommen können, ist, auf die Textknoten des Fragments zuzugreifen und mit ihnen als Zeichenfolge zu arbeiten:

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

wird "2" zurückgeben.

Aus diesem Grund wird in unserem Spiel das Brett (oder das Spielfeld) als Zeichenfolge dargestellt, damit es willkürlich manipuliert werden kann.

Mit XSLT können Sie einen Knotensatz mit dem Konstrukt xsl: for-each iterieren. Aber die Sprache hat nicht die üblichen for- oder while-Schleifen. Stattdessen können Sie einen rekursiven Funktionsaufruf verwenden (Iteration und Rekursion sind isomorph). Eine Schleife des Formulars für x in a..b wird folgendermaßen organisiert:

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

Eine Laufzeit schreiben


Damit das Programm funktioniert, benötigen Sie: 3 XSLT, Quell-XML, Benutzereingaben (Parameter), internes Status-XML und Ausgabe-XML.

Wir platzieren Textfelder mit Bezeichnern in der HTML-Datei : "Präprozessor-xslt", "Prozessor-xslt", "Postprozessor-xslt", "Eingabe-XML", "Parameter", "Ausgabe-XML", "Nachbearbeitete-XML". Wir platzieren / /, um das Ergebnis in die Seite einzubetten (zur Visualisierung).

Fügen Sie zwei Schaltflächen hinzu: Initialisierung und Aufruf (Schritt) des Prozessors.

Schreiben wir einen JavaScript-Code.

Ein wichtiges Merkmal ist die Verwendung der XSLT-Transformation.
 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); } 


Funktionen zum Ausführen des Präprozessors, Prozessors und Postprozessors:
 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; } 


Die Hilfsfunktion parseParams () analysiert Benutzereingaben in Schlüssel = Wert-Paare.

Die Initialisierungstaste wird aufgerufen
 function onInit() { doPreprocessing(); doPostprocessing(); } 


Prozessorstarttaste
 function onStep() { doProcessing(); doPostprocessing(); } 


Die grundlegende Laufzeit ist bereit.

Wie man es benutzt. Fügen Sie drei XSLT-Dokumente in die entsprechenden Felder ein. XML-Eingabedokument einfügen. Drücken Sie die "Init" -Taste. Geben Sie ggf. die gewünschten Werte in das Parameterfeld ein. Klicken Sie auf die Schaltfläche Schritt.

Ein Spiel schreiben


Wenn jemand anderes es nicht erraten hat, ist das interaktive Spiel aus dem Titel das klassische 3 x 3-Tic-Tac-Toe.

Das Spielfeld ist eine 3 x 3-Tabelle, deren Zellen von 1 bis 9 nummeriert sind.
Der menschliche Spieler kreuzt immer (das Symbol "X"), der Computer - Nullen ("O"). Wenn die Zelle mit einem Kreuz oder einer Null belegt ist, wird die entsprechende Ziffer durch das Symbol „X“ oder „O“ ersetzt.

Der Status des Spiels ist in einem XML-Dokument dieser Form enthalten:

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

Das <board /> -Element enthält das Spielfeld. <state /> - der Status des Spiels (Gewinn eines der Spieler oder Unentschieden oder Fehler); Das Element <Anfänger /> wird verwendet, um zu bestimmen, wer das aktuelle Spiel gestartet hat (damit ein anderer Spieler das nächste Spiel startet). <message /> - Nachricht für den Player.

Der Präprozessor generiert aus einem beliebigen XML-Dokument einen Anfangszustand (leeres Feld).

Der Prozessor validiert Benutzereingaben, wendet seine Bewegung an, berechnet den Status des Spiels, berechnet und wendet die Bewegung des Computers an.

Auf Pseudocode sieht es ungefähr so ​​aus
 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 } 


Die Computerbewegungsauswahlfunktion verwendet den Minimax-Algorithmus, bei dem der Computer seine Punktzahl maximiert und die Person sie minimiert. Der Tiefenparameter der Minimax-Funktion wird benötigt, um einen Zug auszuwählen, der bei der geringsten Anzahl von Zügen zum Sieg führt.

Dieser Algorithmus verwendet eine große Anzahl rekursiver Aufrufe und die erste Bewegung des Computers wird auf meinem Computer bis zu 2-3 Sekunden berechnet. Wir müssen irgendwie beschleunigen. Sie können einfach die besten Bewegungen des Computers für alle möglichen akzeptablen Spielfeldbedingungen vornehmen und vorberechnen. Es stellte sich heraus, dass solche Zustände 886 sind. Es ist möglich, diese Anzahl aufgrund von Rotationen und Reflexionen des Feldes zu verringern, dies ist jedoch nicht erforderlich. Die neue Version ist schnell.

Es ist Zeit, das Spielfeld wunderschön darzustellen. Was ist zu verwenden, wenn dies a) Grafiken zeichnen soll (21. Jahrhundert auf dem Hof, welche Art von Spiel ohne Grafiken ?!) Und b) es wünschenswert ist, ein XML-Format zu haben? Natürlich SVG!

Der Postprozessor zeichnet ein kariertes Feld und ordnet darin grüne Kreuze, blaue Nullen und kleine schwarze Kleider an . Es werden auch Meldungen zum Spielende angezeigt.

Und jetzt scheint das Spiel fertig zu sein. Aber etwas stimmt nicht. Um zu spielen, müssen Sie viele unnötige, langweilige und nervige Aktionen ausführen: Geben Sie die Zellennummer in das Feld für den nächsten Code ein und drücken Sie die Taste. Klicken Sie einfach auf die gewünschte Zelle!

Wir finalisieren Laufzeit und Postprozessor .

Fügen Sie zur Laufzeit die Antwortfunktion zum Klicken auf das SVG-Element hinzu:

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

Im Postprozessor fügen wir über jedem Zellenquadrat ein Quadrat hinzu (Transparenz wird durch den rect.btn-Stil angegeben). Wenn Sie darauf klicken, wird eine Funktion mit der Zellennummer aufgerufen:

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

Wenn der Stapel abgeschlossen ist, wird durch Klicken auf eine beliebige Zelle eine neue Zelle gestartet. Die Taste „Init“ muss zu Beginn nur einmal gedrückt werden.

Jetzt können Sie das Spiel als beendet betrachten. Die Sache ist klein: Verstecken Sie die Innenseiten, packen Sie sie in eine Elektronenanwendung, stellen Sie sie auf Steam, ???, wachen Sie reich und berühmt auf.

Fazit


Ein starker Programmierer kann auch in JavaScript alles auf alles schreiben. Es ist jedoch besser, für jede Aufgabe das richtige Werkzeug zu verwenden.

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


All Articles