📜 ⬆️ ⬇️

XSLT Interactive Game

image

Once upon a time, people came up with the XML language and saw that it was good. And they began to use it wherever possible, and even where it should not. Formats of data storage and transfer, configs, web services, databases ... It seemed to look around - XML, XML everywhere. The time has passed, people thought better of it, made up different data formats (or hid the XML inside the archives) and the XML-madness, as it were, died down. But since then, almost any system knows how to integrate such systems (who said Apache Camel?) To the best and easiest way using XML documents.

Where XML is, XSLT is also a language for transforming XML documents. This language is specialized, but has the property of Turing completeness . Therefore, the language is suitable for "abnormal" use. For example, there is a solution to the problem of 8 queens . So you can write a game.
')
For the impatient: a working program on JSFiddle , source code on GitHub .

Any program is engaged in the conversion of input data in the weekend. The program can be divided into three parts: the preprocessor, the processor and the postprocessor. The preprocessor prepares the input data for further processing. The processor is engaged in the main work of data conversion, if necessary, "mixing" user input and external signals and events, including in the loop. The postprocessor is needed to convert the results of the processor into a human-readable (or other program) form.

image

In the case of an interactive XSLT game, each of the three parts of the program will be a separate XSLT file. The preprocessor will prepare the playing field. The processor will apply the move of the human player, make the move of the computer player and determine the winner. The postprocessor will draw the state of the game.

An XSLT program needs a runtime environment. The most common runtime that can execute XSLT is any modern browser . We will use XSLT version 1.0, as it is supported by out-of-box browsers.

A little bit about XSLT and XPath


XSLT is a language for transforming XML documents; XPath is used to access parts of an XML document. The specifications of these languages ​​are published on w3.org: XSLT Version 1.0 and XPath Version 1.0 .

Basics of usage and examples of XSLT and XPath applications are easily searched on the net. Here I will focus on features that need to be considered when trying to use XSLT as a “normal” high-level general-purpose programming language.

XSLT has named functions. They are declared element

<xsl:template name="function_name"/> 

and are called this way:

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

Functions may have parameters.

Announcement:

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

Function call with parameters:

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

Parameters can have default values.

Parameters can be “global” coming from outside. Using these parameters, user input will be passed to the program.

The language also allows you to declare variables that can be associated with a value. Parameters and variables are immutable and values ​​can be assigned to them once (just like in Erlang, for example).

XPath defines four basic data types: a string, a number, a boolean, and a node-set. XSLT adds a fifth type — the result tree fragment. This fragment looks like a node-set, but with it you can perform a limited set of operations. It can be copied entirely into the output XML document, but you cannot access the child nodes.

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

The board variable contains a fragment of the XML document. But the child nodes cannot be accessed. This code is not valid:

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

The best that can be obtained is access to the textual nodes of the fragment and working with them as with a string:

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

will return "2".

Because of this, in our game the board (or playing field) will be represented as a string, so that it can be arbitrarily manipulated.

XSLT allows you to iterate with node-set using the xsl: for-each construct. But the language does not have the usual for or while loops. Instead, you can use recursive function call (iteration and recursion is isomorphic). A loop of the form for x in a..b will be organized like this:

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

We write rantaym


The program requires: 3 XSLT, source XML, user input (parameters), XML internal state and output XML.

We place in the html file text fields with the following identifiers: “preprocessor-xslt”, “processor-xslt”, “postprocessor-xslt”, “input-xml”, “parameters”, “output-xml”, “postprocessed-xml”. We also place /> to embed the result in the page (for visualization).

Add two buttons: initialization and calling (step) of the processor.

Let's write some JavaScript code.

A key feature is the use of XSLT transforms.
 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); } 


Execution functions of the preprocessor, processor and postprocessor:
 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; } 


The helper function parseParams () parses user input into key = value pairs.

Initialize button calls
 function onInit() { doPreprocessing(); doPostprocessing(); } 


Cpu start button
 function onStep() { doProcessing(); doPostprocessing(); } 


Base runtime ready.

How to use it. Insert three XSLT documents into the appropriate fields. Insert XML document input. Press the "Init" button. If necessary, enter the required values ​​in the parameters field. Press the "Step" button.

We write the game


If someone else did not guess, the interactive game from the title is a classic tic-tac-toe 3 to 3.

The game field is a table of 3 to 3, the cells of which are numbered from 1 to 9.
The human player always walks with crosses (the symbol “X”), the computer with zeroes (“O”). If the cell is occupied by a cross or a zero, the corresponding digit is replaced with the symbol “X” or “O”.

The game state is contained in an XML document of this type:

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

The element <board /> contains the playing field; <state /> - the state of the game (the gain of one of the players or a draw or error); the <beginner /> element is used to determine who started the current game (so that another player starts the next one); <message /> - message for the player.

The preprocessor generates the initial state (empty field) from an arbitrary XML document.

The processor validates the user's input, applies his turn, calculates the state of the game, calculates and applies the turn of the computer.

On pseudocode, it looks like this
 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 } 


The function of choosing the course of a computer uses a minimax algorithm, where the computer maximizes its score and the person minimizes. The minimax function's depth parameter is needed to select the turn that leads to the victory in the least number of moves.

This algorithm uses a large number of recursive calls and the first move of the computer is calculated on my machine for up to 2-3 seconds. We must somehow accelerate. You can simply take and pre-calculate the best computer moves for all possible admissible states of the igor field. Such states turned out 886. You can reduce this number due to the turns and reflections of the field, but not necessary. The new version works fast.

It's time to beautifully display the playing field. What to use if this something a) should draw graphics (21st century in the yard, what kind of game without graphics ?!) and b) preferably had the XML format? Of course SVG!

The postprocessor draws a checkered field and arranges green crosses, blue zeros and little black dresses in it . And also shows messages about the end of the game.

And like the game is ready. But something is not right. To play, you need to perform a lot of unnecessary, boring and annoying actions: enter the cell number for the next code in the field and press the button. That would be just to click on the desired cell!

We are finalizing the runtime and postprocessor .

In runtime, add the function of the reaction of clicking on the SVG element:

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

In the postprocessor, we add a transparent one above each cell (the transparency is specified by the rect.btn style) a square, when clicked, the function with the cell number is called:

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

After the end of the game, click on any cell starts a new one The “Init” button needs to be pressed only once at the very beginning.

Now we can assume the game is finished. Things are easy: hide the insides, pack in an electron-application, put it on Steam, ???, wake up rich and famous.

Conclusion


A powerful programmer can write anything on anything, even JavaScript . But it is better for each task to use the appropriate tool.

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


All Articles