
曾几何时,人们想到了XML语言,并认为它很好。 他们开始尽可能地使用它,甚至不应该使用它。 数据存储和传输,配置,Web服务,数据库的格式...看起来四处都是-XML,无处不在的XML。 随着时间的流逝,人们改变了主意,发明了其他各种数据格式(或将XML隐藏在档案中),而XML的疯狂似乎已平息。 但是从那以后,几乎任何系统都能够使用XML并集成这样的系统(谁说Apache Camel?)是使用XML文档的最好,最简单的方法。
在XML的地方,在XSLT那里是一种旨在转换XML文档的语言。 这种语言是专用的,但是
根据Turing的说法具有
完整性 。 因此,该语言适合“异常”使用。 例如,这里有一个
解决8个皇后问题的方法 。 因此,您可以编写游戏。
对于不耐烦的人:
JSFiddle上的一个工作程序,
源于 GitHub 。
任何程序都将输入转换为输出。 程序中可分为三个部分:预处理器,处理器和后处理器。 预处理器准备输入数据以进行进一步处理。 处理器负责数据转换的主要工作,如有必要,将用户输入与外部信号和事件“混合”在一起,包括循环进行。 需要后处理器来将处理器的结果转换为适合人类感知的形式(或通过其他程序)。

对于XSLT上的交互式游戏,该程序的三个部分中的每个部分都是一个单独的XSLT文件。 预处理器将准备比赛场地。 处理器将应用人类玩家的举动,进行计算机玩家的举动并确定获胜者。 后处理器将渲染游戏状态。
XSLT程序需要运行时。 能够执行XSLT的最常见的运行时是任何
现代浏览器 。 我们将使用XSLT 1.0版,因为开箱即用的浏览器都支持它。
关于XSLT和XPath的一些知识
XSLT是一种XML文档转换语言。 XPath用于访问XML文档的某些部分。 这些语言的规范在w3.org上发布:
XSLT版本1.0和
XPath版本1.0 。
XSLT和XPath的基础知识和用法示例可在网上轻松搜索。 在这里,我将提请注意将XSLT用作“常规”通用高级编程语言时需要考虑的功能。
XSLT具有命名功能。 它们被声明为元素。
<xsl:template name="function_name"/>
并以这种方式调用:
<xsl:call-template name="function_name"/>
函数可能具有参数。
公告:
<xsl:template name="add"> <xsl:param name="x"/> <xsl:param name="y"/> <xsl:value-of select="$x + $y"/> </xsl:template>
带参数的函数调用:
<xsl:call-template name="add"> <xsl:with-param name="x" select="1"/> <xsl:with-param name="y" select="2"/> </xsl:call-template>
参数可以具有默认值。
参数可以是来自外部的“全局”。 使用这些参数,用户输入将被传输到程序中。
该语言还允许您声明可能与值关联的变量。 参数和变量是不可变的,并且可以一次为其赋值(例如,就像在Erlang中一样)。
XPath定义了四种基本数据类型:字符串,数字,布尔值和节点集。 XSLT添加了第五种类型-结果树片段。 该片段看起来像一个节点集,但是有了它,您可以执行一组有限的操作。 可以将其完全复制到XML输出文档中,但是您不能访问子节点。
<xsl:variable name="board"> <cell>1</cell> <cell>2</cell> <cell>3</cell> <cell>4</cell> </xsl:variable>
board变量包含XML文档的一部分。 但是子节点无法访问。 此代码无效:
<xsl:for-each select="$board/cell"/>
最好的办法是访问片段的文本节点,并将它们作为字符串使用:
<xsl:value-of select="substring(string($board), 2, 1)"/>
将返回“ 2”。
因此,在我们的游戏中,棋盘(或比赛场地)将显示为字符串,以便可以任意操作。
XSLT允许您使用xsl:for-each构造来迭代节点集。 但是该语言没有通常的for或while循环。 相反,您可以使用递归函数调用(迭代和递归是同构的)。 a..b中x形式的循环将组织如下:
<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>
编写运行时
为了使程序正常工作,您需要:3 XSLT,源XML,用户输入(参数),内部状态XML和输出XML。
我们将带有标识符的文本字段放在
html文件中 :“ preprocessor-xslt”,“ processor-xslt”,“ postprocessor-xslt”,“ input-xml”,“ parameters”,“ output-xml”,“ postprocessed-xml”。 我们还将放置/>以将结果嵌入页面中(用于可视化)。
添加两个按钮:初始化和处理器的调用(步骤)。
让我们编写一些JavaScript代码。
一个关键功能是使用XSLT转换。 function transform(xslt, xml, params) { var processor = new XSLTProcessor(); var parser = new DOMParser(); var xsltDom = parser.parseFromString(xslt, "application/xml");
用于执行预处理器,处理器和后处理器的函数: 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; }
辅助函数parseParams()将用户输入解析为键=值对。
初始化按钮调出 function onInit() { doPreprocessing(); doPostprocessing(); }
处理器启动按钮 function onStep() { doProcessing(); doPostprocessing(); }
基本运行时已准备就绪。
如何使用。 将三个XSLT文档插入相应的字段中。 插入XML输入文档。 按下“初始化”按钮。 如有必要,请在参数字段中输入所需的值。 单击步骤按钮。
编写游戏
如果没有其他人猜到,标题中的互动游戏就是经典的3 x 3井字游戏。
比赛场地是3 x 3的表格,其单元格从1到9编号。
人类玩家总是越过(符号“ X”),而计算机总是过零(“ O”)。 如果单元格被叉号或零占据,则相应的数字将替换为符号“ X”或“ O”。
游戏状态包含在以下格式的XML文档中:
<game> <board>123456789</board> <state></state> <beginner></beginner> <message></message> </game>
<board />元素包含比赛场地; <state />-游戏状态(赢得一名玩家或平局或失误); <beginner />元素用于确定谁开始了当前游戏(以便另一个玩家开始下一个游戏); <message />-给玩家的信息。
预处理器从任意XML文档生成初始状态(空字段)。
处理器验证用户输入,应用其移动,计算游戏状态,计算并应用计算机的移动。
在伪代码上,看起来像这样 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 }
计算机移动选择功能使用最小最大算法,其中计算机将其得分最大化,而人将其最小化。 需要使用minimax函数的depth参数来选择导致最少移动次数获胜的移动。
该算法使用了大量的递归调用,并且计算机上的第一步移动在我的计算机上最多计算了2-3秒。 我们必须以某种方式加速。 您只需为所有可能的可接受赌博场所条件采取并预先计算出计算机的最佳移动方式。 事实证明,这种状态为886。由于磁场的旋转和反射,有可能减少此数目,但这不是必需的。
新版本很快。
是时候漂亮地展示运动场了。 如果这是什么,该怎么使用?a)应该绘制图形(21世纪的院子里,什么样的游戏没有图形?!)并且b)希望具有XML格式? 当然是SVG!
后处理器绘制一个方格场,并在其中布置绿色叉号,蓝色零点和黑色小
礼服 。 它还显示有关游戏结束的消息。
现在好像游戏已经准备就绪。 但是有些事情是不对的。 要播放,您需要执行许多不必要,无聊和烦人的动作:在字段中输入下一个代码的单元格编号,然后按按钮。 只需单击所需的单元格!
我们正在最后确定
运行时和
后处理器 。
在运行时,添加单击SVG元素的响应功能:
function onSvgClick(arg) { document.getElementById("parameters").value = arg; onStep(); }
在后处理器中,我们在每个单元格上方增加一个正方形(透明度由rect.btn样式指定)。单击该正方形时,具有单元格编号的函数称为:
<rect class="btn" x="-23" y="-23" width="45" height="45" onclick="onSvgClick({$index})"/>
批处理完成后,单击任何单元格将启动一个新单元格。 一开始只需要按一次“ Init”按钮。
现在您可以考虑游戏结束了。 事情很小:隐藏内部,将其包装在电子应用程序中,将其放在Steam上,???,醒来就很有名。
结论
一个有主见的程序员
甚至可以用JavaScript编写任何东西。 但是最好为每个任务使用合适的工具。