2011年6月17日金曜日

ブラウザ三国志でダイクストラを行ってみる。



「ブラウザ三国志」というゲームがあります。
そのゲームでは領地の奪い合い、砦攻略を目指して軍を進めていきます。
基本ゲームはしませんが、「ソーシャルゲーム」という分野においての勉強としてずっと続けています。いやバランス良くて本当に面白い。。。ってそんな事やってる場合じゃない><


ChromeExtentionの仕組みみたいな部分での調査が大きいので「処理速度」や汚さは勘弁してください。あくまでそんな事できるんだ。程度でお読みください。

思い立ち

さてブラウザ三国志で砦攻略で重要になってくるのが、座標計算です。
次の砦に向かうという時に領地レベル☆1をめがけて経路を作ってつなげていきます。
自分1人でやっていくのはいいのですが、「同盟」を組んで同盟員とつなげていくと

「今度ここー」
「次私ここー」

みたいな事が起こってきます。
それを掲示板等で共有して進んでいくわけです。ソーシャルですねぇー。

で「ここ」って決めてく経路があるのです。
で、既にツール等もあるのですが、せっかくだから自分で作ってみよー!って思ったわけです。
ひとまずChromeExtensionで行きます。

今回は簡単に「51x51マップ」内で指定した座標間を結ぶ。をやってみましょう。

動きとしては
・51x51マップ時のみに動作する
 ・座標を取得してくる
 ・ダイクストラで経路を算出する
 ・算出した経路をHTML表示する

って流れですかね。

Chrome Extentionを作成してみよう!

さて、まずはボタンを表示してみましょう。
ここを元に構成を勉強して、、、とひとまずHTMLを書けました。

こんな感じ。





ブラ三おなじみのアイコンで、
アイコンをクリックするとHTMLを出力してくれるようになりました。

座標データを収集

さて、このツールは何につけても座標データが存在しないといけません。

って事で
・HTMLのデータを取得
・そこから座標データを抜き出す!
という流れを説明します。

まず51x51マップか、、、という判定。
既存のページ(対象のHTML)の要素を取得するには先ほどのページで使った
「browser_action」とは別に「content_scripts」という仕組みを使用する方法があります。

以下をmanifest.jsonに追記します。
  1. "content_scripts": [  
  2.   {  
  3.     "matches": [  
  4.       "http://m21.3gokushi.jp/big_map.php*"  
  5.     ],  
  6.   
  7.     "js" : [  
  8.       "script.js"  
  9.     ],  
  10.   
  11.     "run_at""document_end",  
  12.     "all_frames"true  
  13. ]  

※要素のつなぎは「,」を忘れずに。

という風に記述します。
matchesはそのページに来たら。jsは使用するJavaScript、run_atはどのタイミングで動作するか?です。
all_frameはすべてのフレームに対して処理を行うか?を記述します。

manifest.jsonは更新したら再読込の必要あります。
これらを思い通りに動かせる(読み込み失敗とかがない)ようになるまでは
Googleのトップページで練習した方が良いかもです。

ブラウザ三国志(mixi)ですが難しいのはページの構成ですね。
URLはもちろんmixiを指しますが、アプリ自体は
「"http://m21.3gokushi.jp/big_map.php*"」にアクセスしています。
なので、それをマッチに入れて、Frameをtrueにしているわけです。

ここまでで、ひとまず「全体地図」のクリックで動作するか確認しておくべきでしょう。

そこまでできたらアプリケーションが使用しているHTMLをハックします。
そうすれば



というコードが存在するので、このデータを

  1. var map51 = document.getElementById( "map51-wrapper" );  
  2. if ( map51 == null ) {  
  3.   return;  
  4. }  


という感じでデータを取得してきます。
その中に
  1. <a   
  2. href=​"/​land.php?x=-22&y=-52#ptop"   
  3. onmouseover=​"return gloss('  
  4.   
  5. <​dl class="​bigmap">  
  6. <​dt class="​bigmap-caption">​空き地<​/​dt>  
  7. <​dd class="​bigmap-subcap">​&​nbsp;​<​/​dd>  
  8. <​dt>​座標&​nbsp;​/​&​nbsp;​距離<​/​dt>​  
  9. <​dd>​(-22,-52)​&​nbsp;​/​&​nbsp;​[35.36]​<​/​dd>​  
  10. <​dt>​戦力<​/​dt>  
  11. <​dd>​★<​/​dd>​  
  12. <​dt class="​bottom-popup-l">​資源<​/​dt>  
  13. <​dd class="​bottom-popup-r">​木1&​nbsp;​岩0&​nbsp;​鉄0&​nbsp;​糧0<​/​dd><​/​dl>​'  
  14.   
  15. )​;​" onmouseout=​"nd()​;​">​  
  16. 1​  
  17. </a>  


というデータがありますので取得します。
  1. // ULタグを取得  
  2. var mapUlList = map51.getElementsByTagName("ul");  
  3. for ( var ulIdx = 0; ulIdx < mapUlList.length; ++ulIdx ) {  
  4.   // 内部のLiタグを取得  
  5.   var mapLiList = mapUlList[ulIdx].getElementsByTagName("li");  
  6.   for ( var liIdx = 0; liIdx < mapUlList.length; ++liIdx ) {  
  7.     // Aタグを取得  
  8.     var linkTag = mapLiList[liIdx].getElementsByTagName("a")[0];  
  9.     dijkstra.pointArray[ulIdx][liIdx] = createPoint(linkTag);  
  10.   }  
  11. }  


createPoint(linkTag)で座標データを解析しています。



相手が存在する場合等もありますから、
x,y,同盟名,lv(領地の強さ)を設定したpointオブジェクトを貯めこんでおきます。
パーサを使用していますが、大きなマップ等になったら変更する予定ではあります。
※51x51ではそんなに重くなかった。

。。。さぁこれで座標データの抜き出しはOKです。
ダイクストラを行ってみましょう!

ダイクストラで最短経路を算出

さて座標データはすべて取得できたので、そのデータから最短経路を算出してみましょう。
実際のソースを貼っておきます。



このコードでは0,0 - 51,51を算出するわけですが、あくまで動作確認ように行なっているだけです。
実際には、始点座標から終点座標を入力するのが良いですね。

実際のコードのcreatePoint()はLvに対してべき乗の重みを付けています。


画面に表示してみよう

さて、座標は判定できたのでそのままHTMLに表示していきます。
  1. route = dijkstra.getRoute(0,0,50,50);  
  2. var pointArray = new Array();  
  3.   
  4. //経路のリストを連想配列に入れる  
  5. for ( var idx = 0; idx < route.length; ++idx ) {  
  6.  var point = route[idx];  
  7.  pointArray[point.arrayX + "," + point.arrayY] = point;  
  8. }  
  9.   
  10. for ( var ulIdx = 0; ulIdx < mapUlList.length; ++ulIdx ) {  
  11.   // 内部のLiタグを取得  
  12.   var mapLiList = mapUlList[ulIdx].getElementsByTagName("li");  
  13.   for ( var liIdx = 0; liIdx < mapUlList.length; ++liIdx ) {  
  14.     
  15.    var idx = pointArray[ulIdx + "," + liIdx];  
  16.    if ( idx === undefined ) {  
  17.     continue;  
  18.    }  
  19.    // Aタグを取得  
  20.    var liTag = mapLiList[liIdx];  
  21.    liTag.style.backgroundColor = "#555555";  
  22.   
  23.   }  
  24. }  
  25.    


配列のポイントがあった方が処理しやすかったので
routeのオブジェクトにarrayX,arrayYを設定しています。

あとはULとLI回回して背景を変更するって感じです。
で出来上がったマップが以下の通り。


コードにより☆5までしか判定してないので最終地点に拠点等が存在すると
経路算出でエラーになります。

ここに載っているように世界にすぐ配信できます。
権利関係微妙ですが他のツール等に比べるとおとなしいので公開しておきました
誰かアイコン作って!(128x128pngしか受け付けない><)

仕事中に呆けて遊んでいたのでやっとアウトプットできた感じ><

今回のマップのダイクストラでの醍醐味は
重さが領地のレベルである事。☆2を進んだ時に遠征だときついだとか
その辺を考慮して重さを設定するところですね。

このソースを利用してマップをDBに貯めこんで計算してとかやれば大規模な遠征の道順等も教えてくれるはずです。
これをつくりはじめた時に、AppEngineを利用して
座標を全ユーザから送信してもらって、それで地図を更新して行くという仕様を考えましたけど
本気で行くと有料レベルになると思って止めました。
※お金発生してもOKですけど、お金取る(CM)とか入れると厄介かなぁと。。。

今回はゲーム周りでしたが、Tweetボタンやいいねボタンがないようなページを
簡単に共有したり、その他他人のWebページに1つエッセンスを加えて処理をやりやすくしたり、
Webのマクロみたいなイメージで処理する事が可能になります。

ChromeExtentionはChromeOSの登場によって
ChromeOS上で動作するアプリケーションを作れる事になります。
ChromeOSがどの程度市場を席巻するかは不明ですが、1つのアプリの公開形態としては
注視しておいても良いでしょう。

さぁ同盟員に書簡して使ってもらおう!

0 件のコメント: