1人のJoclyプログラマーの知識

「郵便配達員スタンレーの学生」とグロッシュはつぶやいた。

「孤児です。」 非常に悲しい話...いい子、怒っていないなら、

...私の言いたいことを理解しているなら。

「ええと...多分。」 モクリストは言って、急いでスタンリーに向かった。

「ピンについて何か知っていますか?...」

-Netser! -スタンリーと答えた...

「ピンに関するすべてを知っています!」



サー・テリー・プラチェット「 名誉



1998年、 Zillions of Gamesはボードゲームのファンの間で人気を博しましたが 、欠陥がないわけではありませんでした。 その主な欠点は近さでした。 デモキットに含まれている48のゲームのセットを超える何かをプレイするには、プログラムをアクティブ化するためにお金を払わなければなりませんでした。 Windows以外でZoGを実行することは不可能でした(このOSの一部のバージョンで問題が発生した可能性があります)。 ネットワークモードはローカルネットワーク上またはモデム経由のみでしたが、Webは意図されていませんでした。 それについて何もすることはありません、それは閉じられた製品です! また、現在のところ、実際にはサポートされていません。 上記の欠点のない代替手段があることを聞いて、多くの人が喜んでいると思います。 このJoclyに会いましょう。



Joclyの開発者は、Zillions of Gamesの例に触発されましたが、根本的に異なる道を歩みました。 当初から、Webは最前線にありました。 Joclyアプリケーションは、モバイルを含むあらゆるプラットフォームの最新のブラウザーで実行できます! ほとんどの場合、最新の3Dインターフェイスを使用できますが、互換性の問題が発生した場合、Joclyは独自に2Dに切り替えます。 コンピューターと他の人の両方でプレイしたり、以前にプレイしたゲームを表示したり、ビデオチャットで他のプレイヤーとチャットしたりすることもできます。 ここでは、製品の機能の簡単な説明と、Zillions of Games との比較を見ることができます。



もちろん、このような巨大な蜂蜜の樽は、軟膏に小さなハエなくしてはできません(誰かのようですが)。 JoclyはZRFGDLなどのDSLをサポートしていません。開発は純粋で単純なJavaScriptで行わなければなりません。 開発者自身は、これがより時間のかかるアプローチであることを認めていますが、それは巨大なプラスを持っています-JavaScriptではほとんど何でも記述できます。 むしろ、Jocly自体がいくつかの制限を課していないかどうか説明することができます。 現在の実装では、完全な情報とランダムイベントのない2プレーヤーゲームのみがサポートされています。 私が理解しているように、これらのかなり厳しい制限は、使用されるAIアルゴリズム( アルファ-ベータおよびUCT モンテカルロ )に関連しています。





おそらく、開発者は私の意見では最も重要なことをしたということです-彼らゲームモデルを視覚的表現から分離しまし 。 それと別の両方を別々に書くことができます! モデルに取り組んでいるプログラマーは、視覚化の問題から完全に注意をそらすことができ、プレゼンテーションを綿密に取り上げて、通常の2D(ZoGで唯一可能なもの)に加えて、正直な3Dインターフェイスを実現できます。 それは難しいですが、かなり実行可能です。 必要に応じて、 Blenderで描画して独自の形状設計を開発することできます。



理解する最良の方法は、ごく小さなことでも、自分で何かをすることです。 プロジェクト作成者のウィキにはチェスのカスタマイズに関する資料がすでにあったので、チェッカーに目を向けることにしました。 Jocly Inspectorを使用して、実装の詳細を表示しました。 利用できるのは、「国際」、「英語」、「スペイン語」、「ブラジルのドラフト」です。 ロシアの草案以外 。 しかし、何かない場合-あなたはそれを行う必要があります!



コンピューターでJoclyアプリケーションを起動できます(開発者によると、これがカスタマイズされたアプリケーションを起動する唯一の方法です)。 Jocly jQueryプラグインがこれを支援します。 以下 、適切な例とその機能のデモを示します。 開始するには、 jquery.jocly.min.jsjquery.jocly.min.css 、および小さなhtmlファイルの3つのファイルのみが必要です。 すべてを「正しい方法で」行う場合は、それらをすべてのWebサーバー( Apacheなど )のドキュメントディレクトリに配置する必要がありますが、実際に示されているように、 FireFoxを使用する場合は、HTMLファイルをアップロードするだけです(他のブラウザーではこのトリックはありません)働いた)。



それが含まれているものです
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <link rel="stylesheet" href="jquery.jocly.min.css"> <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> <script src="jquery.jocly.min.js"></script> <title>Jocly development stub web page</title> <script> $(document).ready(function() { $("#applet").jocly({}); $("#applet").jocly("localPlay","custom-draughts",{ }); $("#applet").jocly("setFeatures",{ notifyEnd: false, hasEndSound: false, }); $("#options").joclyListener("listen","viewOptions",function(message) { console.log("viewOptions",message); $("#options-skin").hide().children("option").remove(); if(message.options.skin && message.skins && message.skins.length>0) { message.skins.forEach(function(skin) { $("<option/>").attr("value",skin.name).text(skin.title).appendTo($("#options-skin")); }); $("#options-skin").show().val(message.options.skin); } $("#options-notation").hide(); if(message.options.notation!==undefined) $("#options-notation").show().children("input").prop("checked",message.options.notation); $("#options-moves").hide(); if(message.options.moves!==undefined) $("#options-moves").show().children("input").prop("checked",message.options.moves); $("#options-autocomplete").hide(); if(message.options.autocomplete!==undefined) $("#options-autocomplete").show().children("input").prop("checked",message.options.autocomplete); $("#options-sounds").hide(); if(message.options.sounds!==undefined) $("#options-sounds").show().children("input").prop("checked",message.options.sounds); $("#options").show(); }); $("#options").on("change",function() { var options={}; if($("#options-skin").is(":visible")) options.skin=$("#options-skin").val(); if($("#options-notation").is(":visible")) options.notation=$("#options-notation-input").prop("checked"); if($("#options-moves").is(":visible")) options.moves=$("#options-moves-input").prop("checked"); if($("#options-autocomplete").is(":visible")) options.autocomplete=$("#options-autocomplete-input").prop("checked"); if($("#options-sounds").is(":visible")) options.sounds=$("#options-sounds-input").prop("checked"); $("#applet").jocly("viewOptions",options); }); var defaultLevel=0; $("#mode-panel").joclyListener("listen","players",function(message) { console.warn("players",message); function UpdatePlayer(player,key,levels) { if(player.type=="computer") { var select=$("#select-level-"+key); select.empty(); for(var i=0;i<levels.length;i++) $("<option/>").attr("value",i).text(levels[i].label).appendTo(select); select.val(player.level); $("#level-"+key).show(); } else $("#level-"+key).hide(); } UpdatePlayer(message.players[1],'a',message.levels); UpdatePlayer(message.players[-1],'b',message.levels); var modeSelect=$("#mode"); modeSelect.show(); if(message.players[1].type=="self" && message.players[-1].type=="self") modeSelect.val("self-self"); else if(message.players[1].type=="self" && message.players[-1].type=="computer") modeSelect.val("self-comp"); else if(message.players[1].type=="computer" && message.players[-1].type=="self") modeSelect.val("comp-self"); else if(message.players[1].type=="computer" && message.players[-1].type=="computer") modeSelect.val("comp-comp"); else modeSelect.hide(); message.levels.forEach(function(level,index) { if(level.isDefault) defaultLevel=index; }); $("#mode-panel").show(); }); $("#mode-panel").on("change",function() { console.log("changed mode",$("#mode").val(),$("#select-level-a").val(),$("#select-level-b").val()); var players; switch($("#mode").val()) { case "self-self": players={"1":{type:"self"},"-1":{type:"self"}}; break; case "self-comp": players={"1":{type:"self"},"-1":{type:"computer",level:$("#select-level-b").val() || defaultLevel}}; break; case "comp-self": players={"1":{type:"computer",level:$("#select-level-a").val() || defaultLevel},"-1":{type:"self"}}; break; case "comp-comp": players={"1":{type:"computer",level:$("#select-level-a").val() || defaultLevel}, "-1":{type:"computer",level:$("#select-level-b").val() || defaultLevel}}; break; } $("#applet").jocly("setPlayers",players); }); $("#restart").on("click",function() { $("#applet").jocly("restartGame"); }); $("#takeback").on("click",function() { $("#applet").jocly("takeBack"); }); $("#fullscreen").on("click",function() { $("#applet").joclyFullscreen(); }); }); </script> <style type="text/css"> * { box-sizing: border-box; } body { } #container { width: 100%; display: table; table-layout: fixed; } #applet { display: table-cell; width: 60%; } #controls { display: table-cell; width: 33%; vertical-align: top; padding: 0 .5em 0 .5em; } .box { background-color: #f0f0f0; border: 2px solid #e0e0e0; border-radius: 1em; padding: 1em; } </style> <script type="text/jocly-model-view" data-jocly-game="draughts/custom-draughts"> <!--     --> </script> </head> <body> <div id="container"> <div id="applet"></div> <div id="controls"> <div id="mode-panel" style="display: none;" class="box"> <h3>Controls</h3> <button id="restart">Restart game</button><br/><br/> <button id="takeback">Take back</button><br/><br/> <select id="mode"> <option value="self-self">Self / Self</option> <option value="self-comp">Self / Computer</option> <option value="comp-self">Computer / Self</option> <option value="comp-comp">Computer / Computer</option> </select><br/><br/> <label id="level-a" for="select-level-a">Computer(A) level<br/> <select id="select-level-a"></select><br/><br/> </label> <label id="level-b" for="select-level-b">Computer(B) level<br/> <select id="select-level-b"></select><br/><br/> </label> <button id="fullscreen">Full screen</button><br/><br/> </div> <br/> <div id="options" style="display: none;" class="box"> <h3>Options</h3> <select id="options-skin"></select><br/><br/> <label id="options-notation" for="options-notation-input"> <input id="options-notation-input" type="checkbox"/> Notation<br/> </label> <label id="options-moves" for="options-moves-input"> <input id="options-moves-input" type="checkbox"/> Show possible moves<br/> </label> <label id="options-autocomplete" for="options-autocomplete-input"> <input id="options-autocomplete-input" type="checkbox"/> Auto-complete moves<br/> </label> <label id="options-sounds" for="options-sounds-input"> <input id="options-sounds-input" type="checkbox"/> Sounds<br/> </label> </div> </div> </div> </body> </html>
      
      







ゲームを簡単に起動するには、 このマニュアルで説明されている最小限のhtmlファイルを使用できますが、 より完全なバージョンで作業する方がはるかに便利です。 次に、htmlファイルにゲームのJSON記述を含める必要があります。 ここには微妙なポイントがあります。 このバージョンのゲームは「カスタムドラフト」と呼ばれます(この名前はファイル内で2回見つかります)。 Jocly Inspectorのテキストフィールド全体からゲームの説明を取得できますが、ファイルの一部のみを変更した場合、これは冗長になる可能性があります。 変更したモデルの部分のみを説明するだけで十分です。残りはJoclyがWebサイトから取得しますが、これを機能させるには、名前を次のように構成する必要があります:「 ドラフト /カスタムドラフト」。 スラッシュの前の名前の部分は、ある種の「親」ゲームの名前であり、そこからすべての欠落が取得されます。 繰り返しますが、完全なJSON記述を使用する場合、名前のこの部分は必要ありません



必要なものはこれだけ
  <script type="text/jocly-model-view" data-jocly-game="draughts/custom-draughts"> { "view": { "js": [ "checkers-xd-view.js", "draughts8-xd-view.js" ] }, "model": { "js": [ "checkersbase-custom-model.js", "draughts-model.js" ], "gameOptions": { "preventRepeat": true, "width": 4, "height": 8, "initial": { "a": [[0,0],[0,1],[0,2],[0,3],[1,0],[1,1],[1,2],[1,3],[2,0],[2,1],[2,2],[2,3]], "b": [[7,0],[7,1],[7,2],[7,3],[6,0],[6,1],[6,2],[6,3],[5,0],[5,1],[5,2],[5,3]] }, "variant": { "compulsoryCatch": true, "canStepBack": false, "mustMoveForward": false, "mustMoveForwardStrict": true, "lastRowFreeze": false, "lastRowCrown": true, "captureLongestLine": true, "kingCaptureShort": false, "canCaptureBackward": true, "longRangeKing": true, "captureInstantRemove": false, "lastRowFactor": 0.001 }, "uctTransposition": "state" } } } </script> <script type="text/jocly-resources" data-jocly-game="custom-draughts"> { "checkersbase-custom-model.js": "checkersbase-custom-model.js" } </script>
      
      







まず、ボードのサイズとピースの初期配置の説明が印象的です(後者はすべてのJoclyゲームで利用できるわけではありません)。 ボードが4x8として記述されていること(対角チェッカーシステムで使用されていないフィールドはモデルによって記述されていない)と、数字を配置するためのすべてのインデックスがゼロから始まるという事実に慣れるのは少し難しいです。 以下は、チェッカーゲームを記述するのに十分な(開発者の観点から)ブール設定のリストです。 補充します。 すべての設定を示す必要はありません。完全にリストを作成しましたが、これは単に便宜上のことです。 サーバーから提供するすべてのファイルを「 text / jocly-resources 」で記述することが重要です。 ファイル「 checkersbase-custom-model.js 」は、変更されるモデルの一部です。 最初は、これはファイル「 checkersbase-model.js 」の単なるコピーです。



私たちが何を変えるかを考える時です。 「ブラジル人」と「ロシア人チェッカー」の違いは何ですか(Joclyキットで入手可能)。 実際、たった2つの「ささいなこと」です。 ブラジルのドラフトは、国際またはポーランドのドラフトのルールに従ってプレイされますが、8x8のボードでプレイされます。 「多数決ルール」があります。2つ以上のキャプチャオプションから、プレーヤーは、品質に関係なく、相手のチェッカーの最大数を「削減」するものを選択する必要があります。 「ロシアの草案」では、オプションを無効にする必要があります。 これにより、すべてがシンプルになり、プロパティはブール値の「 captureLongestLine 」設定によって制御されます。



ちなみに
Joclyのチェッカーでルールのほとんどがどのように実装されているかを見るのは興味深いです。 複合的な動きが全体として考慮される場合、タスクは簡単になります。 移動「 Model.Board._GenerateMoves 」を生成するメソッドの最後に、次のコードフラグメントがあります。



生成された移動のリストから選択します
 ... if(aGame.g.captureLongestLine) { var moves0=this.mMoves; var moves1=[]; var bestLength=0; for(var i in moves0) { var move=moves0[i]; if(move.pos.length==bestLength) moves1.push(move); else if(move.pos.length>bestLength) { moves1=[move]; bestLength=move.pos.length; } } this.mMoves=moves1; } ...
      
      







動きのリスト(この表現またはその表現)があり、そこから最大数のピースをとる動きのみを選択する必要があります(Joclyの解釈では、最大数のステップで構成されます)。 ZoGでは、「部分的な」移動の概念により、同様の機能を実装するために、アプリケーションに直接「 最大キャプチャ 」ハードコードオプションを追加する必要がありました。



別のルールでは、さらに困難が生じます。チェッカーが一連のキャプチャ中に女性になった場合、変換後、すでに女性のルールに従って、彼女は停止せずに「カット」し続けます。 「インターナショナル」および「ブラジルのチェッカー」では、別のルールが適用されます。チェッカーが一連のキャプチャの最後の行にあり、単純なチェッカーの役割でさらに勝てる場合は、戦闘を続けてターンしません ! コード内で変換が行われる場所を見つけます。



これはメソッド `` Model.Board.ApplyMove ''
 Model.Board.ApplyMove = function(aGame,move) { + var pieceCrowned=false; var WIDTH=aGame.mOptions.width; var HEIGHT=aGame.mOptions.height; var pos0=move.pos[0]; var pIndex=this.board[pos0]; var piece=this.pieces[pIndex]; var player=piece.s; piece.l=pos0; var toBeRemoved={}; this.zSign=aGame.zobrist.update(this.zSign,"board",piece.s+"/"+piece.t,piece.p); for(var i=1;i<move.pos.length;i++) { var pos=move.pos[i]; this.board[piece.p]=-1; piece.p=pos; + if (aGame.g.russianCustom==true) { + var r=aGame.g.Coord[pos][0]; + if((player==JocGame.PLAYER_A && r==HEIGHT-1) || (player==JocGame.PLAYER_B && r==0)) { + pieceCrowned=true; + } + } this.board[pos]=pIndex; var caught=move.capt[i]; if(caught!=null) { if(this.board[caught]>=0) toBeRemoved[this.board[caught]]=true; this.board[caught]=-1; } pos0=pos; } this.zSign=aGame.zobrist.update(this.zSign,"board",piece.s+"/"+piece.t,pos); var plp=move.capt[move.capt.length-1] piece.plp=plp?plp:move.pos[move.pos.length-2]; for(var index in toBeRemoved) { var piece0=this.pieces[index]; var other=(1-piece0.s)/2; this.pCount[other]--; switch(piece0.t) { case 0: this.spCount[other]--; break; case 1: this.kpCount[other]--; break; } this.zSign=aGame.zobrist.update(this.zSign,"board",piece0.s+"/"+piece0.t,piece0.p); this.pieces[index]=null; } if(aGame.g.lastRowCrown && this.pieces[pIndex].t==0) { var r=aGame.g.Coord[move.pos[move.pos.length-1]][0]; - if((player==JocGame.PLAYER_A && r==HEIGHT-1) || (player==JocGame.PLAYER_B && r==0)) { + if(pieceCrowned || (player==JocGame.PLAYER_A && r==HEIGHT-1) || (player==JocGame.PLAYER_B && r==0)) { var piece0=this.pieces[pIndex]; piece0.t=1; var self=(1-player)/2; this.spCount[self]--; this.kpCount[self]++; this.zSign=aGame.zobrist.update(this.zSign,"board",piece0.s+"/0",piece0.p); this.zSign=aGame.zobrist.update(this.zSign,"board",piece0.s+"/1",piece0.p); } } }
      
      







行った変更、およびボードのモデル、動き、ピースなどが直感的とはほど遠いことに気付くかもしれません。 コードは多くの追加アクション( Zobrist Hashの計算など)を実行しますが、このすべてにおいて迷子になることはまったく困難ではありません。 これはZRFではありません! 変更の本質は単純です-最後の水平線(黒の最初の水平線)を通過するという事実を覚えており、それが行われた場合、移動の終わりに変換の水平線にあるかのようにピースを変換します。 仕組みを見てみましょう。







すべてが正しいようです。 変換はコースの最後ではなく、その過程で発生するという事実に注意を払いません。 モデルの現在の実装では、移動の途中でピースを回転させることは良い考えではありません(すべてが壊れる、と私はチェックしました)! しかし、私たちはすべてに備えましたか? 位置を少し変更します。







はい、これは私たちが恐れていたものです。 最後の水平に達したチェッカーは、さらに女性のように「食べる」権利があることを「知りません」! 彼女に説明しよう。 動きをするとき、誰が誰を食べるかについての決定はすでに少し遅れています。 動きを生成する方法、つまり「 catchPieces 」関数で正しい場所を探すのは論理的です。 「 キング 」フラグは彼女の最後のパラメーターに渡され、女性を扱っていることを示します。 最後の水平線を渡すときに変更してみましょう:



私はすぐにそのようなことを考えませんでした。
 function catchPieces(pos,poss,capts,dirs,king) { while(true) { var nextPoss=[]; var nextCapts=[]; var nextDirs=[]; aGame.CheckersEachDirection(pos,function(pos0,dir) { var r; if(aGame.g.canCaptureBackward==false) r=aGame.g.Coord[pos][0]; var dir0=aGame.Checkers2WaysDirections[dir]; + if (aGame.g.russianCustom==true) { + if($this.board[pos0]>=0 && $this.pieces[$this.board[pos0]].s==-$this.mWho) { + var pp=aGame.g.Graph[pos0][dir]; + if (aGame.g.Coord[pp]) { + var rr=aGame.g.Coord[pp][0]; + var HEIGHT=aGame.mOptions.height; + if(($this.mWho==JocGame.PLAYER_A && rr==HEIGHT-1) || + ($this.mWho==JocGame.PLAYER_B && rr==0)) { + king=true; + } + } + } + } if(!king) { if($this.board[pos0]>=0 && $this.pieces[$this.board[pos0]].s==-$this.mWho) { var r0,forward; if(aGame.g.canCaptureBackward==false) { r0=aGame.g.Coord[pos0][0]; forward=false; if(($this.mWho==JocGame.PLAYER_A && r0>=r) || ($this.mWho==JocGame.PLAYER_B && r0<=r)) forward=true; } if(aGame.g.canCaptureBackward || forward==true) { var pos1=aGame.g.Graph[pos0][dir]; if(pos1!=null && ($this.board[pos1]==-1 || pos1==poss[0])) { var keep=true; for(var i=0;i<dirs.length;i++) if((aGame.g.captureInstantRemove && capts[i]==pos0) || (aGame.g.captureInstantRemove==false && capts[i]==pos0 && dirs[i]==dir0)) { keep=false; break; } if(keep) { nextPoss.push(pos1); nextCapts.push(pos0); nextDirs.push(dir0); } } } } } else { // king if(aGame.g.longRangeKing) while($this.board[pos0]==-1 || (aGame.g.king180deg && pos0!=null && capts.indexOf(pos0)>=0)) pos0=aGame.g.Graph[pos0][dir]; if(pos0!=null) { if($this.board[pos0]>=0 && $this.pieces[$this.board[pos0]].s==-$this.mWho) { var caught=pos0; pos0=aGame.g.Graph[pos0][dir]; if(aGame.g.kingCaptureShort) { if($this.board[pos0]==-1 || pos0==poss[0]) { var keep=true; for(var i=0;i<dirs.length;i++) if(!aGame.g.king180deg) { if((aGame.g.captureInstantRemove && capts[i]==caught) || (aGame.g.captureInstantRemove==false && capts[i]==caught && dirs[i]==dir0)) { keep=false; break; } } else if(capts[i]==caught) { keep=false; break; } if(keep) { nextPoss.push(pos0); nextCapts.push(caught); nextDirs.push(dir0); } pos0=aGame.g.Graph[pos0][dir]; } } else { while($this.board[pos0]==-1 || pos0==poss[0]) { var keep=true; for(var i=0;i<dirs.length;i++) if((aGame.g.captureInstantRemove && capts[i]==caught) || (aGame.g.captureInstantRemove==false && capts[i]==caught && dirs[i]==dir0)) { keep=false; break; } if(keep) { nextPoss.push(pos0); nextCapts.push(caught); nextDirs.push(dir0); } pos0=aGame.g.Graph[pos0][dir]; } } } } } return true; }); if(nextPoss.length==0) { if(poss.length>1) $this.mMoves.push({ pos: poss, capt: capts }); break; } if(!aGame.g.compulsoryCatch && poss.length>1) { var poss1=[]; for(var i=0;i<poss.length;i++) poss1.push(poss[i]); var capts1=[]; for(var i=0;i<capts.length;i++) capts1.push(capts[i]); $this.mMoves.push({ pos: poss1, capt: capts1 }); } if(nextPoss.length==1) { pos=nextPoss[0]; poss.push(pos); capts.push(nextCapts[0]); dirs.push(nextDirs[0]); } else { for(var i=0;i<nextPoss.length;i++) { var poss1=[]; for(var j=0;j<poss.length;j++) poss1.push(poss[j]); poss1.push(nextPoss[i]); var capts1=[]; for(var j=0;j<capts.length;j++) capts1.push(capts[j]); capts1.push(nextCapts[i]); var dirs1=[]; for(var j=0;j<dirs.length;j++) dirs1.push(dirs[j]); dirs1.push(nextDirs[i]); catchPieces(nextPoss[i],poss1,capts1,dirs1,king); } break; } } }
      
      







女性のサインがパラメーターとして関数に渡されることは非常に幸運でした。 移動ジェネレーターは、可能なすべての複合移動のツリーを走査します 。 フィギュアのオブジェクトで女性のサインが変わった場合、ジェネレーター自体がモデルに加えた変更のロールバックに注意する必要があります。 そうしないと、プログラムが予期しない動作をする可能性があります。 Axiomでこれがどのように行われるかをご覧ください。



カスタムエンジン
 : Custom-Engine ( -- ) -10000 BestScore ! 0 Nodes ! $FirstMove BEGIN $CloneBoard DUP $MoveString CurrentMove! DUP .moveCFA EXECUTE MaxDepth Depth ! 0 EvalCount ! BestScore @ 10000 turn-offset next-turn-offset Score 0 5 $RAND-WITHIN + BestScore @ OVER < IF DUP BestScore ! Score! 0 Depth! DUP $MoveString BestMove! ELSE DROP ENDIF $DeallocateBoard Nodes ++ Nodes @ Nodes! $Yield $NextMove DUP NOT UNTIL DROP ;
      
      







ここでは、ボードの内容を( $ CloneBoardを呼び出して )一時配列にコピーし、「最適な」移動を選択して、ボードの一時状態( $ DeallocateBoard )を削除します。 そして-表示レベルごとに! とにかく、今ではすべてが意図したとおりに機能します:







これがすべてだとは思わないでください。 Joclyにはまだ頭を痛めるものがあります! トルコのドラフトでこのビデオゲームの何が問題なのかわかるかどうかを確認してください。







答え
これは少しわかりにくいトピックです。 ドラフトのほとんどの最新バージョンでは、「 トルコストライキ 」ルールが適用されます。複雑なキャプチャの過程で、相手の駒はすぐにボードから取り除かれず、取られたとマークされるだけです。 彼らはコースの終わりに一度に登ります。 このルールは、「 トルコのドラフト 」を除き、ほぼすべての場所に適用されます! トルコの草案では、女性は恐るべき力です。 キャプチャを実行すると、彼女は後続の移動のために自分の場所を「クリア」します。 1人の女性だけが一度に敵の軍隊全体食べることができます!



ビデオから判断すると、これはJoclyではそうではありません。 最後のステップで、女王は以前のチェッカーに邪魔されてボードから取り外されていないため、キャプチャのより長いチェーンを選択できないことは明らかです。 ボードゲームから遠く離れた人々は、この状況は取るに足りないと感じるかもしれませんが、これらのルールによってトルコのドラフトをプレイする真剣なプレイヤーはいません! 今のところ、私はそれを修正する方法を知りません。 必要な修正は、この記事で説明したカスタマイズよりも複雑です。 ムーブジェネレーターに変更を加えることで、クイーンを以前に撮影したチェッカーを「見えないようにする」ことができますが、加えて、クイーンに、そのようなフィールドでの移動を完了する可能性を含め、撮影したチェッカーが占有しているフィールドで停止する機会を提供する必要があります。 難しいですし、今すぐにそれをする準備はできていませんが、読者の何人かは実用的な解決策を提案するでしょうか?



抽象ボードゲームの開発のための別の興味深い「エンジン」に会いました。 独自の制限があり、開発プロセスは単純ではありません。 しかし、彼は独自の完全にキラー機能セットを持っています! オープンで、クロスプラットフォームで、Web指向であり、最も重要なのは、開発者によってまだサポートされていることです! プロジェクトは生きています! 参加してください。おそらく伝説のZillions of Gamesよりもはるかに長生きするでしょう。




All Articles