XSLTのインタラクティブゲーム

画像



むかしむかし、人々はXML言語を思いつき、それが良いことだと考えました。 そして、彼らは可能な限りそれを使用し始めました。 データの保存と転送、設定、Webサービス、データベースのフォーマット...見回されているようです-XML、XMLどこでも。 時間が経つにつれて、人々は考えを変え、他のさまざまなデータ形式を発明し(またはアーカイブ内にXMLを隠した)、XMLの狂気は静まったように見えました。 しかし、それ以来、ほとんどすべてのシステムがXMLを作成し、そのようなシステムを統合することができました(Apache Camelとは誰が言ったのですか?)



XMLには、XMLドキュメントを変換するために設計された言語であるXSLTがあります。 この言語は特殊化されていますが、チューリングによると完全性の特性があります。 したがって、この言語は「異常な」使用に適しています。 ここでは、たとえば、 8クイーンの問題に対する解決策があります 。 だから、ゲームを書くことができます。



せっかちな人のために: JSFiddleの作業プログラム、 GitHubのソース。



どのプログラムも入力を出力に変換します。 プログラムでは、プリプロセッサ、プロセッサ、およびポストプロセッサの3つの部分を区別できます。 プリプロセッサは、さらに処理するために入力データを準備します。 プロセッサは、必要に応じて、ユーザー入力と外部信号およびサイクルを含むイベントを「混合」するデータ変換の主要な作業に従事しています。 プロセッサの結果を人間の知覚に適した形式に(または他のプログラムによって)変換するには、ポストプロセッサが必要です。



画像



XSLTでのインタラクティブゲームの場合、プログラムの3つの部分はそれぞれ個別の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は、文字列、数値、ブール値、ノードセットの4つの基本データ型を定義します。 XSLTは、5番目のタイプ-結果ツリーフラグメントを追加します。 このフラグメントはノードセットのように見えますが、これを使用すると、限られた一連の操作を実行できます。 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-fileに識別子を持つテキストフィールドを配置します 。「preprocessor-xslt」、「processor-xslt」、「postprocessor-xslt」、「input-xml」、「parameters」、「output-xml」、「postprocessed-xml」です。 また、ページに結果を埋め込むために/>を配置します(視覚化のため)。



2つのボタンを追加します:初期化とプロセッサの呼び出し(ステップ)。



JavaScriptコードを書きましょう。



重要な機能は、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); }
      
      







プリプロセッサー、プロセッサー、およびポストプロセッサーの機能:
 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()は、ユーザー入力をkey = valueのペアに解析します。



初期化ボタンが呼び出されます
 function onInit() { doPreprocessing(); doPostprocessing(); }
      
      







プロセッサー開始ボタン
 function onStep() { doProcessing(); doPostprocessing(); }
      
      







基本的なランタイムの準備ができました。



使い方。 3つのXSLTドキュメントを適切なフィールドに挿入します。 XML入力ドキュメントを挿入します。 「初期化」ボタンを押します。 必要に応じて、パラメータフィールドに必要な値を入力します。 [ステップ]ボタンをクリックします。



ゲームを書く



他の誰かが推測しなかった場合、タイトルからのインタラクティブなゲームは、古典的な3 x 3の三目並べです。



競技場は3行3列のテーブルで、そのセルには1から9までの番号が付けられています。

人間のプレイヤーは常にコンピューター(シンボル「X」)、ゼロ(「O」)を横切ります。 セルが十字またはゼロで占められている場合、対応する数字は記号「X」または「O」に置き換えられます。



ゲームの状態は、次の形式のXMLドキュメントに含まれています。



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





<board />要素には競技場が含まれています。 <state />-ゲームの状態(プレイヤーの1人に勝つか、ドローまたはミス)。 <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 }
      
      







コンピューターの移動選択機能は、ミニマックスアルゴリズムを使用します。コンピューターは、スコアを最大化し、人が最小化します。 ミニマックス関数の深度パラメーターは、最小の移動数で勝利につながる移動を選択するために必要です。



このアルゴリズムは多数の再帰呼び出しを使用し、コンピューターの最初の動きは最大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})"/>
      
      





バッチが完了した後、任意のセルをクリックすると新しいセルが開始されます。 「初期化」ボタンは最初に一度だけ押す必要があります。



これで、ゲームの終了を検討できます。 ポイントは小さい:内部を隠し、電子アプリケーションにパックし、Steamに置き、???、豊かで有名な目を覚ます。



おわりに



気の強いプログラマーは、JavaScriptでも何でも書くことができます 。 ただし、各タスクに適切なツールを使用することをお勧めします。



All Articles