hartmannのゲームあれこれ

適当にやってるゲームのあれこれを偶に書きます

WoT CWE管理シート自動化の旅々 12.『ありとあらゆるありふれたCWE管理シートの物語』

ts-hartmann.hatenablog.com

これの続き

お約束1

 CWEの物語をしましょう。私は副指令であり、GM担当官です。
 CWEは出会いと別れの連続で、同時に選択の連続でもあります。
 いくつもの後戻りできない選択をして、私は今ここにいるのです。

やりたいこと

 前回、表にマークをつけるついでにメモを挿入しましたが、そのメモをなんちゃらかんちゃらして指揮官ログを取ってしまおうという感じです。

 今まで、私がシートやGMの管理をやっていたクランではCWE中、各戦闘終了毎、指揮官にフォームでログを書いてもらっていました(IGN・相手クラン・マップ・勝敗)。

f:id:TS_hartmann:20201220165653p:plain
こんな感じのやつ
 この作業をなくしてしまおうというわけです。今まで指揮官ログとして集めていた情報は先の4つの情報なわけなのですが、4つとも前回上書きした後のメモ書き内に情報として入っています。
f:id:TS_hartmann:20201221010127p:plain
表にマークつけた後のメモ書き
 このメモ書きから4つの情報を抜き出して、一つの指揮官ログとして出してしまおうよというのが今回の内容になっております。  

指揮官ログの記入自動化

手順1. GAS側の記述

 それではいつも通りGASのコードからです。

function commanderLog() {

  var id = "スプレッドシートのid";  
  var spreadSheet = SpreadsheetApp.openById(id);  
  var sheetName = "戦闘予定(CWE用)";
  var sh = spreadSheet.getSheetByName(sheetName); //メモ書きを上書きするため
  
  var note = sh.getRange("P11:BC109").getNotes(); //メモ書きの参照
  var Arr = Array.prototype.concat.apply([],note);//2次元配列を1次元配列にする
  var Arr2 = Arr.filter(function(s){return s !== '';}); //1次元配列の空白要素を削除する
  var count = Arr2.length; //配列変数Arrの配列数を調べる
  
  var data = []; //データ格納用空白定義
  var data3 = []; //データ格納用空白定義
  for ( var i = 0; i < count; i++) {
       var time = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm');

      if(Arr2[i].match(/Commander:    (.+)/) == null){
        var commander = ["?","?"];
      }else{
        var commander = Arr2[i].match(/Commander:    (.+)/);
      }

      if(Arr2[i].match(/Enemy clan name:    (.+)/) == null){
        var enemy = ["?","?"];
      }else{
        var enemy = Arr2[i].match(/Enemy clan name:    (.+)/);
      }

      var map = Arr2[i].match(/Map name:    (.+)/);

      if(Arr2[i].match(/Result:    (.+)/) == null){
        var result = ["?","?"];
      }else{
        var result = Arr2[i].match(/Result:    (.+)/);
      }
      var format = Arr2[i].match(/format:    (.+)/);
      data.push([time,commander[1],enemy[1],map[1],result[1]])
  }
  
  var result = data.filter(function(value){return (value[1] !== '?')}); //指揮官名が?になっている要素の配列を削除
  var count2 = result.length; //配列変数resultの配列数を調べる
    
  var sheetName2 = "指揮官ログ";
  var sh2 = spreadSheet.getSheetByName(sheetName2); //メモ書きを加えていくため
  
  for ( var j = 0; j < count2; j++) {
     sh2.appendRow(result[j])
     }
  
  Logger.log(result);
}

 スプレッドシートのidは使っているシートのidを、シートの名前(sheetNameのところ)は使用している予定表シートの名前を入れてください。

 指揮官名・敵クラン名・マップ名・結果の4つのデータに加えて現在時刻を出力する理由は、次の回の内容である指揮官統計を取る時にデータの絞り込みをするためです。  

手順2. スプレッドシート側の記述

 「指揮官ログ」の名前にしたシートを用意してください。2行目以降の左の列から時刻・指揮官名・敵クラン名・マップ名・結果の順に出力されますので、分かりやすいよう1行目に項目を書いておくといいかもしれません(ログは上書きされることなく、新しい行に記載されていきます)。  下の画像みたいになれば成功です。  

f:id:TS_hartmann:20201222200516p:plain
こんな感じ

お約束2

私「あれ......?夢……?」
私「さて、行きましょうか!」


 はいっ、それでは問題です!いろいろなコードを書く、近くに人がいたのなら間違いなく誰もが振り返り、ため息をこぼしてしまうほどの乱雑なコードは一体誰のでしょう?それは、運営をしてCWEの、管理シートの物語を綴り続ける、そう私です!



 私が誰かとぶつかる……

??「ごめんなさい、(タクを)書くのに夢中で……」
私「いえいえ、こちらこそ……」
私「でも、指揮するときは周りを見た方がいいですね。視野を自ら狭めているとしか言いようがありません」
??「ごめん、ありがとう……あっ(新しいタクを思いつく)」
私「ま、次からはお互い気を付けましょ」
??「は、はい(礼をして立ち去る)」


 当時の私はまだ知りませんでした。この先、彼が立派な指揮官になることを。彼の名は「The Laughing Hand」



 「魔女の旅々」は第2クールやりそうな終わり方でしたね。この話はあと1話分続く予定ですが、もし第2クールがあるとしたらなんとか24話分まで書けることがあるのかないのか……ものすごく不安です……

WoT CWE管理シート自動化の旅々 11.『2人の指揮官』

ts-hartmann.hatenablog.com

これの続き

お約束1

ねろさん「【報酬車両の獲得枠が足りない?あのベトナム業者が2年ぶりに帰ってきたのか】って……」
ねろさん「あれだけ現地で社会問題になった業者がっ!中国に対抗するのでしょうか?それとも模倣犯?」


一般兵A「……それよりあなた、あの話ご存知?」
一般兵B「やだわぁ……ベトナム業者らしきクランが復活したそうね」
一般兵C「とっくに消滅したもんだと思っていたのに。うちの車両大丈夫かしら……」
一般兵A「またGMが荒らされるのかしらねえ……」
一般兵B「怖いわぁ……」

ねろさん「嘆いている場合じゃなさそうですね。面倒は避けたいですし……うん!」

ねろさん「よし!どこからどう見てもBANされているようにしか見えません!ただのもぬけの殻です!」
ねろさん「さぁー車両を選ぶことにしましょー(BAN)」

やりたいこと

 前回の続きです。前回の記事では、戦闘ログを取得するところまでは終わっているので今回はそのログを戦闘予定表に反映させるところをやります。
 +αで後のために、その時の指揮官も記録にとってセル内メモの上書きもしたいなと思っています。

f:id:TS_hartmann:20201214041809p:plain

f:id:TS_hartmann:20201218160602p:plain
戦闘ログを基に、この表にマークを付けていきます

結果ログの表反映自動化

手順1. GAS側の記述

 いつものごとく先にコードから。前回のコードの続きに書いてください。
 多重にfor文を使っている上、それぞれにgetRangeとか使っているせいで重い処理になっておりますがご勘弁を。軽くする方法が分からなかったんです。

function logData1() {

  //戦闘ログをマークとして表に反映

  var id = "スプレッドシートのid";  
  var spreadSheet = SpreadsheetApp.openById(id);  
  var sheetName = "data_log2";
  var cell = spreadSheet.getSheetByName(sheetName).getRange("B2:E100").getValues(); //province,map,enemy clan,win or lose
  var result = cell.filter(function(value){return (value[0].length > 0)}); //空白要素の配列を削除
  var count = result.length; //配列変数resultの配列数を調べる
 
  
  var col = 8; //H列
  var sheetName2 = "対戦相手判別用";
  var sh = spreadSheet.getSheetByName(sheetName2); //記入セルを判断するため
  var sheetName3 = "戦闘予定(CWE用)";
  var sh2 = spreadSheet.getSheetByName(sheetName3); //防衛かどうか判断するため&メモの上書きに使用
  var key = []; //検索名を配列として入れるための空白定義(Province name)
  var int = []; //プロヴィンス名が一致するセルの行を配列として入れるための空白定義
  var key2 = []; //検索名を配列として入れるための空白定義(enemy clan name)
  var count2 = sh.getDataRange().getLastColumn()-15;//最終列-15
  var debug = []; //デバッグ用空白定義
  
  for(var i = 0; i < count; i++){
     var keyi = result[i][0];
     key.push(keyi);
     var key2i = result[i][2];
     key2.push(key2i);
     var result2 = result[i][3];
     
         
     for(var j = 0; j < count; j++){
     
      if(sh.getRange(j+11,col).getValue() === keyi){ //行番号検索※VLOOKUP関数みたいな働き
         var row2 = (j+11);
         
         
         for(var k = 0; k < count2; k++){  
         
           if(sh.getRange(row2,k+16).getValue() === key2i){ //列番号検索※HLOOKUP関数みたいな働き
               var col2 = (k+16);
               
               
               if(result2 === "勝利"){
                 var result3 = "○";
                 var mat = sh.getRange(row2,12).getValue();
                 
                   if(mat.match(/防衛/)){
                     sh2.getRange(row2,15).setValue("防衛");
                     for (var a = 0; a < count2 + 15 - col2; a++) {
                         sh2.getRange(row2,a + 1 + col2).setValue("-"); //該当セルより同じ列で右側にあるセルに-と入力する
                         }
                     }else{              
                          if(sh2.getRange(row2,col2 + 2 ).getValue() === "-")
                            sh2.getRange(row2,15).setValue("獲得");
                   }
                     
               }
               if(result2 === "敗北"){
                 var result3 = "×";
                 sh2.getRange(row2,15).setValue("敗退");
               }
               if(result2 === "引き分け"){
                 if(mat.match(/防衛/)){
                   var result3 = "△";
                   sh2.getRange(row2,15).setValue("防衛");
                   }
                   for (var b = 0; b < count2 + 15 - col2; b++) {
                         sh2.getRange(row2,b + 1 + col2).setValue("-"); //該当セルより同じ列で右側にあるセルに-と入力する
                         }
                   var result3 = "▲"
                   sh2.getRange(row2,15).setValue("敗退");
               }
              int.push([row2,col2,result3]); 
                           
        }
      }
      
       }
     }
     
   }
   
   var count3 = int.length; //配列変数intの配列数を調べる
   
   
   for (var x = 0; x < count3; x++) {
        if(!(sh2.getRange(int[x][0],int[x][1]).getValue() == int[x][2])){
          sh2.getRange(int[x][0],int[x][1]).setValue(int[x][2]); //該当するセルに試合結果のマークを入力
  
           if (int[x][2] === "×" || int[x][2] === "▲"){
              var memo = sh2.getRange(int[x][0],int[x][1]).getNote(); //該当するセルのメモを参照
              var commander = sh2.getRange(int[x][0],10).getValue(); //メモが埋め込まれている行の指揮官名を取得
       
              sh2.getRange(int[x][0],int[x][1]).clearNote(); //該当するセルのメモを消去
              sh2.getRange(int[x][0],int[x][1]).setNote( memo + String.fromCharCode(10) + String.fromCharCode(10) + "Commander:    " + commander + String.fromCharCode(10) + "Result:    " + "敗北"); //該当するセルのメモを記入
              for (var y = 0; y < count2 + 15 - int[x][1]; y++) {
                  sh2.getRange(int[x][0],y + 1 + int[x][1] ).setValue("-"); //該当セルより同じ列で右側にあるセルに-と入力する
                  }
              }
           if (int[x][2] === "○" || int[x][2] === "△"){
              var memo = sh2.getRange(int[x][0],int[x][1]).getNote(); //該当するセルのメモを参照
              var commander = sh2.getRange(int[x][0],10).getValue(); //メモが埋め込まれている行の指揮官名を取得
       
              sh2.getRange(int[x][0],int[x][1]).clearNote(); //該当するセルのメモを消去
              sh2.getRange(int[x][0],int[x][1]).setNote( memo + String.fromCharCode(10) + String.fromCharCode(10) + "Commander:    " + commander + String.fromCharCode(10) + "Result:    " + "勝利"); //該当するセルのメモを記入
          
              }
          }               
       }   
   
   Logger.log(int);
   
}


 いつも通り、スプレッドシートのidには使用しているスプレッドシートのidを入れてください。シートの名前(SheetName3の所)も使っている予定表のシート名に変えてください。

 上半分ではマークを付けるセル位置の判定を、下半分では実際にマークを付ける工程をやっています。

 第5話で「対戦相手判別用」のシートを用意したのは、この処理のために必要だからでした。

手順2. スプレッドシート側の記述

 今までの回で作ったシートが全部あるなら、新規のシートは必要ありません。追記も必要ありません。

 コード実行後、画像のように○とかのマークがついていれば成功です。

f:id:TS_hartmann:20201219103044p:plain
表反映、よし!

 ちなみに第3話からのマッチング情報取得→敵クラン名取得→表反映→戦闘ログ取得→表反映の一連の動作を稼働すると、戦闘予定の領土数にもよりますが私の環境だと20領土で3分くらいの稼働時間がかかります。

 まだ、高速化とか全く学んでないので許して…許して……

 リファクタリングの概念とかCWE中に初めて知ったのじゃ……

お約束2

はんどさん「うわぁぁ嫌です嫌だ指揮したくないです。僕は楽して車両取りたいんですー」
ねろさん「(苦笑い)」
私「わがまま言ってんな。戦闘あんだから次やるぞ」
はんどさん「じゃあ指揮官辞めます」
私「だったらどうやって稼いでくんだよ」
はんどさん「コネで車両取らせてもらいますー」
ねろ「あ、無理です」
私「だってよ」
りょうさん「はんどさん、諦めて(にらみつける)」
ねろさん「敵視されてる……ものすごい敵視されてる……」
私「じゃあな」
つるさん「ええ。また半年後に」
はんどさん「やーだぁー……いやーだぁー……ねろさーん……(助けて)」

ts-hartmann.hatenablog.com

WoT CWE管理シート自動化の旅々 10.『2人のGM』

この話から「お約束」の内容は内輪ネタに近い内容が多いので注意

CWEも終わり、リアルの方も落ち着いてきたので更新再開します。

これの続き ts-hartmann.hatenablog.com

お約束1

はんどさん「ずいぶん古いタクですね……なんです?これ」
私「知らん。いつの間にかタクを管理してる場所に貼られていたらしい」
はんどさん「はあ……で、これをどうしろと?」
私「ここから海を越えて西にずっと進んだ先に、のどかな海岸C1ってポジションがあるんだがな……そこでの頭出しの打ち合いに役立つかもって話だ」
はんどさん「はあ……」
私「それを使って勝てばいいんだ。ちょろいだろ?」
はんどさん「えっ……それだけですか?こんなちょろい作業どうしてししょーがやらないんですか?(無茶ぶりだろ)」
私「私はこれからGMだからな」
はんどさん「GM?」
私「戦闘出すぎて疲れてよー」
はんどさん「はあ……(逃げたなてめー)」


私「くれぐれもそのタク以外のことをやるなって話だ。なんか皆の気が狂ってしまうかもな」
はんどさん「あいあいさー!絶対にこのタク以外はやりません!じゃ、行ってきまーす。ふっふぅー!」
私「さてと、じゃあ私もやるとするかな」

やりたいこと

 戦闘ログ取りたい。最終的には戦闘予定表の右側の表に結果を記入したい。

f:id:TS_hartmann:20201218132501p:plain
こういった表を自動で作ってくれれば便利ですよねえ……

戦闘ログを取得する手順

手順1. GAS側の記述

 多分これです。GMから直接データ取ってきてます。5話に書いてあるコードをトリガーにして実行するといいかもしれません。

function getLog() {
 
   //JavaScriptを使ったデータのスクレイピング※phantomjscloudを使用(クランCW戦闘ログの抽出)
 
  const URL = 'https://asia.wargaming.net/globalmap/#clanlog/clanID'; //Grobal Map for world of Tanks clan battle's log(TANOC)
  var key = 'Apikey';
  
  
  var option = 
      {url:URL,
       renderType:"HTML",
       outputAsJson:true};
  var payload = JSON.stringify(option);
  payload = encodeURIComponent(payload);
  var url = "https://phantomjscloud.com/api/browser/v2/"+ key +"/?request=" + payload;                
  var response = UrlFetchApp.fetch(url);
 
  var json = JSON.parse(response.getContentText()); 
  var source = json["content"]["data"];
  
  
  //logファイルにログを書き出す

  var id = "スプレッドシートのid";  
  var spreadSheet = SpreadsheetApp.openById(id);  
  var sheetName = "data_log";
  var str = source;
  var result = str.substr(95000);//n文字以降の文字のみ表示※削除する文字数は各CW・CWEごとに仕様を合わせる
  

  var ary = result.split(/(?=The clan lost a battle against the)|(?=The clan defeated the)/); //The clan lost a battle against theかThe clan defeated theで文字列を区切って配列格納
  var count = ary.length; //配列変数aryの配列数を調べる
  
  Logger.log(result);
  
  spreadSheet.getSheetByName(sheetName).getRange("A1:D1000").clearContent(); //セル内のコンテンツ削除※値や数式
  
  for (var i = 0; i < count; i++) {
  spreadSheet.getSheetByName(sheetName).getRange(i + 1, 1).setValue(ary[i]); //配列変数aryのi+1番目に格納されている値をセルAi+1に入力
  }
  
  logData1();
}

 clanIDには自クランのid、Apikeyには自分のphatomjscloudアカウントのApikeyを、スプレッドシートのidには使用しているスプレッドシートのIDを代入してください。  

手順2. スプレッドシート側の記述

 「data_log」「data_log2」のシートを作成します。それぞれのシートの役割はこんな感じです。

  1. 「data_log」: CW戦闘ログをGMからスクレイピングした内容が書かれているシートです。
  2. 「data_log2」: 「data_log」の内容を見やすいように加工。


 「data_log2」のいくつかのセルに以下のように記入をしてください。

  1. A2セル:=IFERROR(TEXT(TEXT(REGEXEXTRACT(data_log!$A2,"data-role="&CHAR(34)&"time"&CHAR(34)&">(.+?)</span>"),"[hh]:mm")+'Province参照'!$F$1,"[hh]:mm"))
  2. B2セル:=IFERROR(REGEXEXTRACT(data_log!$A2,"data-action="&CHAR(34)&"select-province"&CHAR(34)&">(.+?)</a> province."))
  3. C2セル:=iferror(if(B2="","",query('Province参照'!$A:$D,"select D where C ='"&B2&"'")))
  4. D2セル:=IFERROR(REGEXEXTRACT(data_log!$A2,"> \[(.+?)\]</span>"))
  5. E2セル:=IFERROR(IF(REGEXEXTRACT(data_log!$A2,"(.+?) <span class="&CHAR(34)&"clan-name"&CHAR(34)&">")="The clan lost a battle against the","敗北",IF(REGEXEXTRACT(data_log!A2,"(.+?) <span class="&CHAR(34)&"clan-name"&CHAR(34)&">")="The clan defeated the","勝利","")))


 これで手順は以上となります。これをなんとかして戦闘予定表に記入するのです。

f:id:TS_hartmann:20201214041809p:plain
こんな感じにデータ加工できていれば成功

お約束2

(会議中……)

つるさん「あなた変わりましたよねえ……」
私「別に。まあでも、合流すぐの頃よりは今の関係の方が好きかな。楽だし」
つるさん「ほんとに変わりましたね」
私「お互い(GM管理が)上手くなったってことだろ。さて、日を跨いじまう、急ぐぞ。」
私「?(何かを思い出す)」
私「あのタクティクス……あれは!」
私「今後の予定変更だ!」
つるさん「ちょっとどうしたんです?」
私「のどかな海岸の戦闘に行くぞ!!」
つるさん「もう唐突ですね、相変わらず……待ってくださーい」


 その日、私(ねろさん)はあるマップの指揮をやろうとしていました。そこは私の得意マップで、C1のポジションにおいて頭出しで殴り合い、そのまま決着が着く展開を良く見るチーフの聖地。そう、のどかじゃないマップ「のどかな海岸」です。

ts-hartmann.hatenablog.com

続き

WoT CWE「ルネサンス」はてな戦線

はじめに

  戦車兵の皆さんは2週間お疲れ様です。CWEに参戦せずに高みの見物だけしていた方は、戦車から逃げないでください。はるとまんでございます。

 

 1/11,6時~1/25,6時の期間でCWE「ルネサンス」が開催されました。当然のようにGM担当官?をさせられたのでまあその視点でお気持ち表明しておきます(大体4,000字くらい)。今回も誰かお気持ち表明ブログをまとめてくれると嬉しいなあ……(丸投げ)

 

 CWE開催前のお話

 CWEの情報が出てくるまでは、CWEに参加するつもりは一切なかった。新車両出るって知らなかったし。それもあってか、TANOCでは私がいない前提で運営を行う体制が作られていた。そのおかげで運営やる人は6人もいた(そのうち半分は補佐だったが)。

 

 CWEの情報が出て、新車両が出るって分かってからは「かっこいい…ほしい…」となって参加することになった。私がCWEに参加することを快諾してくれたことにとても感謝しているし、同時に私が参加する分他の人の枠を奪ってしまったこと(これに関してはTANOCのアクティブが50数人規模だったのでそれほど問題ではなかった)、私がGMをやることで他の人の仕事を奪ってしまったことに関して非常に申し訳なく思う。

 

 せっかくGM兼シート管理やるんだから今まで忌避していたスプレッドシートの自動化やるかぁってことで、独学でプログラミングを学んだりした。結局バグ取りとかで最初の2日間はシートを手動で動かすことになったけど……

 

クラン順位など結果に関してのお話

 

f:id:TS_hartmann:20210128082014p:plain

5戦以上参加人数に関してはうちのクランが一瞬乗っ取られたので正確じゃないかも

 

 クラン順位 6位

 車両獲得数/5戦以上参加人数 44人/56人(獲得率78.57%)

 

 うん、私としては満足(数字はBAN前)。正直つまらない理由のBANさえなければ9割狙えてたのでは?とは思わないではないが、まあそこは飲み込んでおこう。CWE途中で7日BAN喰らった6人はどうやっても引き上げることが出来ない(BAN前に最終ボーダー超えていれば別だが)。

 

 最初に言っておくが、TANOCは最初からクラン順位は狙っていない。7日目で撤退しなかった時点でクラン順位を狙っていないことは察することが出来るとは思うが……

 

 車両獲得率向上が第一モットーで、そのためならクラン名声は投げ捨てる覚悟だった。じゃあなんでクラン名声投げ捨ててないの?って疑問が湧くと思うが、これに関しては「領地からのボーナス向上」研究の効果のおかげだ。

 

 この研究なのだが、領地から得られる無料申請分も倍増していくのだ。TANOCは最後の+400%以外は研究していたので「領地からのボーナス向上+1850%」であった。上級無料申請分にもこのボーナスが適用されていたので、基本で無料申請がある領土さえ保有しておけば上級無料申請が毎日19個使えるというウハウハ状態だったのだ。

 

 こんな状態だったので基本領土は上級無料申請領土だけ持って、後は上級とエリートで引き上げをって感じでやっていた。本当は上級だけでよかったんだけど……後、無料申請倍増は次回以降修正されそう……

 

 クラン順位は最後の方にOPA、3Uに捲られてしまったがこれに関しては許してほしい。いかんせん元々の出力の差が違う。こちらが最大上級3部隊を限界としているのに対してあいつらは上級5部隊とか平気でやってくる。こんな状態でよく終盤になるまで抜かされなかったよ。と私は思っている。正直こんなアクティブ人数でこの順位取れたなあと。順位狙っていないにもかかわらず。

 

GM戦略や体制についてのお話

  先に書いた通り、もともと私がいない前提でGM体制が組まれていたので運営の人数自体は前回より豊富だった。と言ってもGMで部隊投げたり、戦闘予定組んだりする人は私抜きで4人体制だったけど……後の3人は主に記録係などだった。ついでに私はメインGMはするつもりがなかったので、会議には参加していたけど会議の最中はほとんど寝ていた。いわゆるおさぼりというやつである。

 

 それを毎日やっていたら私が大バッシングを食らうわけなので、初日・再上陸の日(6日目)・再々上陸の日(11日目?)・最終日の戦闘予定だけは私が組んだ。まあ私が組んだ日の戦闘は上手くいっていたから仕事はしたんじゃないかな、うん。あとクラン研究とかいう楽しいお買い物もやった。そのクラン研究で20万くらいクラン名声生み出したんじゃないかなと思っている。

 

戦闘のパフォーマンス的なお話

 こう言うとTANOCのメンバーには失礼かもしれないが、CWE前直近の進撃戦に参加していて「こりゃ今回のCWEはあんまし期待できないな」とか思っていた。だって結果良くなかったし。あと指揮官が前回より少なかった。それが良い意味で期待を裏切られましたね。エリート3領地取ったし。

 

 なぜエリート投げたの?って話ですが、そこに砂の川ってマップがあったからです。2日目、再上陸の日と2回とも上陸には成功しているので正しい判断だったと思う。だってDUCKYクラスの相手だったとしても砂の川なら75%勝てるんだもん。というわけで我々はエリート領土で引き上げをやっていた。

 

 良い意味で期待を裏切られた理由は、直前になって上手い助っ人勢が集まってきたからでしょうね。やっぱり以前のCWEとかでちゃんと実績残しておくと人は集まるものだなあ……ほとんどもうだめさんの人望のおかげだけど。

 

 後はまあはんどさんが上手いこと砂の川指揮で圧勝してくれたことが大きい。前回のCWEもそうだったけど今回のCWEでもはんどさんはMVPである。引き上げが楽になったのははんどさんの貢献によるところが大きいし、クラン名声的にも相当稼いできてくれた。はんどさんを今回のCWEに参加させるということが私の一番の仕事でしたとさ……

 

f:id:TS_hartmann:20210202035104p:plain

エリート領地にWGのお墓が建っているのをWGはどういう気持ちで見ているんでしょうね?そもそもGMを見ていないか……

 

 なんかトナメ芋が流行っていたらしいが、私たちとしては上級ばっかりやっていたので無問題である。どうせトナメ芋なんてされても2Artyで10,000くらいダメージ入れるから普通に崩せる。

 

 

CWE管理シート自動化についてのお話

 今までの私のブログを読んでくれていた方ならわかると思うが、今回のシート関係はほとんど自動化した。まあ最初に書いた通り最初の2日間はバグ取りに時間を費やされた(運営の仕事が出来なかった)。CWとかいう本番環境を事前に用意してくれなかったWGが悪いということにしておこう。

 

 逆に言えばその2日間以外はほとんど問題はなかったので戦闘相手の自動取得とか、マッチング情報をbotでDiscordに流したりだとか、結果の自動記入とかスムーズにできた(37行目以降は結果が記入されないとかいう問題はあったけど)。特に戦闘予定botは好評だったので今後も使っていきたいと思う。

 

 今まで書いてたシート自動化の連載はあと3話分の原稿が残っている。早く上げよう……

 

その他雑談

 必要研究ポイントの変更はとてもいい変更だった。エリート領地が本当においしい領地に変化したし、上級無料申請が大量にあってオークション額を考慮しなくてもよかったのはGM的にも相当楽だった。この点はWGを素直に褒める。ただ逆に撤退&投資は100%投資しか出来なくなったっていうのは、選択肢が減るという点でどうなんですかねWGさん?まあGMの負担を多少は減らしてくれたのだから感謝しよう。

 

 後は1500位ボーダーが予想よりも高かったことかな。37,000pt~38,000ptくらいになるのかなと予想していたけど蓋を開けてみれば40,336ptまで上がった。とはいっても元々40,000ptまでは引き上げておこうねとしていたので1戦勝たせるだけで済んだけど。

 

 最後にTANOCで一緒に戦ってくれた皆さんありがとう。ペナ回避のためだけに来てくれた英霊の皆さんにも感謝を。まざーさんもチップ投げやってくれてありがとう。おかげで私は早く寝ることが出来た。

 

 これで私のCWEは最後にしたいな。最後にいい結果残せたし。まあ次も誰かに叩き起こされてやることになるとは思うんだけど……

 

 

お墓

worldoftanks.asia

f:id:TS_hartmann:20210202043816p:plain

f:id:TS_hartmann:20210202043830p:plain

f:id:TS_hartmann:20210202061024p:plain

1枚目はボーダー推移、2枚目は上級オークションの最低落札額、3枚目は各日本人クランの車両獲得率(5戦以上参加、BAN前、抜けがあったら許してね)
 

裏話的なもの(※内輪ネタ注意)

8日目終了後のある時

tyrion(DUCKYの運営)「TANOCの砂の川まじでつえーな。CWE終わるまでに勝って見せるわ。」

             ↓

翌日TANOCが砂の川でDUCKYに対して2連勝する。

             ↓

さらにその翌日(10日目終了後)……

tyrion「おれかしこいから、今日はTANOCの砂の川に(師団を)投げなかったぜ。」

 

 クラメンから「tyrionからこういうメッセが来てたよ」という感じで伝えられたもの。訳は意訳なのであしからず(全て15vs15の話)。

 

 完全な即落ち2コマである。今回のCWEの中で一番笑った瞬間だった。ちなみにTANOCの砂の川とはエリート領地の話である。このメッセが来て以降、DUCKYと砂の川で当たる時はAngelinaやRainbowが両方いるやべー本軍が毎回来るようになった。結局別の日に2敗してしまったわけだが、13日目でエリート砂の川にて勝利できたので勝ち逃げである。

 

 前回の4DENINさんといい……いい意味でやべー奴らに挑まれてるなあ。さすがはんどさん。あと「おれかしこい」って言うなら師団おいてなかったから投げてきてほしかったなあ……と思った。

 

 

その2

 

 続いてはこれ。「なんでTANOC師団おいてないの?最後戦いたかったのに~」的な感じでたクランの運営さんに怒られた(どこだったかは忘れた。多分これもtyrionからだったような気がする)。防衛を1戦入れるくらいだったら、上級上陸で数戦やった方がいいんだわ。ごめんねDUCKY。

 

 

 

 

 

 

WoT CWE管理シート自動化の旅々 番外編1.『トリガーみたいな何か』

お約束1

 本当は本編内に入れたかったのですが、そうすると「魔女の旅々」とのタイトル名でのつながりがなくなってしまうので、番外編として書くことにし、CWEが始まる前に投稿することになりました。

 番外編では怪文書は書きません。いたってまじめな文章です(どこが?)。

やりたいこと

 擬似トリガーを作ります。トリガーの使用用途は、第3話で作ったマッチング情報を取得するスクリプトを15分おきに動かすために使用します。下の画像のように元々GASに用意されているトリガー設定で「15分おき」で設定してもいいのですが、これで動かすトリガーは最初の起動時間に依存してしまいます。例えば最初の起動が22分だった場合は次の起動が37分となり、その後15分ごとに起動という形になります。

 しかし、「特別戦」が建つ前に(あるいはできるだけ早く)予定表内にメモを埋め込まないと指揮官のためになりませんので、スクリプトが起動する時間はマッチングが新しく起こる15分・30分・45分・00分にした方がいいでしょう(ここではラグも考慮して、それぞれ+1分にしています)。  

f:id:TS_hartmann:20210101204807p:plain
時間主導型のトリガーは最初に起動した時間に依存する

  それなら最初の起動を15分にしてしまえば、後の起動が30分・45分・00分になるじゃないかと思う方もいるでしょうが、面倒くさい仕様があるのです。それは手動でテスト起動した場合でも「最初の起動時間」が更新されてしまうのです。これではテストのために手動でスクリプトを動かすなんてできなくなりますよね。

 私もいろいろ調べましたが、指定した時間に動作させるトリガーを作る方法は書いてあったものの、他のトリガーも一緒くたに運用している場合は面倒なコードを書かなければならず……といい方法はどこにも書いていないのでした......

 そこで思いついた方法が「時間の判定をして、特定の時間だったら次のスクリプトを回す」という処理を毎分ごとのトリガーで行うということなのでした……  

擬似トリガーの作成

手順1. GAS側の記述

 いつも通りまずはコードからです。

function triggerAt() {

  //指定時間トリガー
 
  var now = new Date();
  var mins = now.getMinutes();
  
  if(mins == "16.0"|mins == "31.0"|mins == "46.0"|mins == "1.0"){
     Scraping2();
     Logger.log("次のコードを回す");
    }
  Logger.log(mins);
}

 これを毎分起動するだけです。多分16分、31分、46分、01分だけ次のコードを回す擬似トリガーとなっているでしょう。

手順2. スプレッドシート側の記述

 ありません。

お約束2

 ありません。

WoT CWE管理シート自動化の旅々 9.『翻訳の嘆き』

 新年あけましておめでとうございます。この連載も年越し連載となりましたが、今後ともよろしくお願いします。

これの続き ts-hartmann.hatenablog.com

はじめに

私「(おなかが鳴る音)」

 クラン「TANOC」が使うDiscordの平壌にいる、金欠でおなかを空かして、今にも泣いてしまいそうな儚くも働き者の担当官は誰でしょう。そう、私です。悲しいことに私です。

私「はぁ……」

f:id:TS_hartmann:20210101035955p:plain
さみしい

やりたいこと

 この連載の2話でプロヴィンス情報を取得したことを覚えているでしょうか?そこで取得したプロヴィンス名は英語表記でした。ただ、英語表記だと何か違和感がある。じゃあ何とか日本語表記にできないか?という感じです。

 Googletranslate関数使えばいいじゃんと思ったあなた、甘いです。Googletranslate関数は地名の翻訳においては精度がかなり低いです。普通の英文のように翻訳してしまいます。

 そこで、少しでも精度を上げつつお金をかけないで翻訳するために、ウェブアプリケーションを作って翻訳APIから翻訳をかけます(翻訳結果はGoogle翻訳APIと同じです、多分)。

f:id:TS_hartmann:20201217163023p:plain
AlburyをGoogletranslate関数で翻訳をかけた結果、ソミュールと出るのはバグ?別の地名だし……

 E列にはAPIから翻訳をかけた結果、F列にはGoogletranslate関数で翻訳をかけた結果が書いてます。地名の翻訳でGoogletranslate関数が使えないことは一目で分かるでしょう。

 ちなみに私はこれらを使っていません。全て翻訳できるわけじゃないし、それなら普通に英語表記のまま使った方が楽ですよね。  

プロヴィンス名を翻訳する手順

手順1. 翻訳APIの作成

qiita.com

 こちらで紹介されている通りにウェブアプリケーションを作成してください。
 出来たら次に行きます。  

手順2. GAS側の記述

 次にコードを書きます。

function translate() {

  //プロヴィンス名(英語)取得

   var id = "スプレッドシートのid";  
   var spreadSheet = SpreadsheetApp.openById(id);  
   var sheetName = "Province参照";
   var cell = spreadSheet.getSheetByName(sheetName).getRange("C2:C500").getValues();
   var result = cell.filter(function(value){return (value[0].length > 0)});//空白要素の配列を削除
   var Arr = Array.prototype.concat.apply([],result); //2次元配列を1次元配列にする
   var count = Arr.length; //配列変数Arrの配列数を調べる
  
  //URL、データ取得のループ処理
   var ary = [];
   for ( var k = 0; k < count; k++) {
       var URL = '外部アプリケーションのURL'+Arr[k]+'&source=en&target=ja';//翻訳API呼び出し
       var response = UrlFetchApp.fetch(URL);
       var response2 = response.toString();
       var mat = response2.match(/"text":"(.+?)"/);
       ary.push(mat);
  }
  
    var count2 = ary.length; //配列変数aryの配列数を調べる
    spreadSheet.getSheetByName(sheetName).getRange("E1:E1000").clearContent();//セル内のコンテンツ削除※値や数式
   
    for (var i = 0; i < count2; i++) {
        spreadSheet.getSheetByName(sheetName).getRange(i + 2, 5).setValue(ary[i][1]); //配列変数aryのi+1番目に格納されている値をセルEi+2に入力
    }
  
   Logger.log(ary);
     
}

 スプレッドシートのidには使用しているスプレッドシートのidを、外部アプリケーションのURLはhttps://script.google.com/macrosから始まるURL(text=まで)を入れてください。

 新しく追加するシートはありません。上手くいけばコードを実行するだけで、画像のようにプロヴィンス名の日本語表記がマップ名の隣に追加されているでしょう。

f:id:TS_hartmann:20201217162211p:plain
これで日本語表記じゃないと心配する勢も安心

 注意点は関数を使うよりも精度が高いとはいえ、地名翻訳の精度は100%ではないということ。地名翻訳が間違っている所は、手動にて翻訳を直す必要があります。
 地名翻訳サービス使わないでもなかなかの精度です。流石Google翻訳……

終わりに

私「止められなかった……」
私「彼は2度も……その手でクランを……」
私「私はただの副指令……ただのGM……」
私「未熟で……何もできないで……」

※特定の人を指すわけではありません

 続きの記事はCWEが始まってからになるでしょう。もしくはCWE中はとても忙しいので終わってからになるかもしれません。2週間後も各クランお互い頑張りましょうね!

ts-hartmann.hatenablog.com

続き

WoT CWE管理シート自動化の旅々 8.『シート壊し魔』

ts-hartmann.hatenablog.com

 これの続き

 GASでシート自動化楽しいですね。もう止まらなくなりそうです。元々「GASとか全く分からないのでシート自動化はちょっと……」だったのが、関数ゴリゴリに書くのが好きだったこともあってはまってしまいました。最近は家にいる間ずっとゲーム感覚でシートの自動化をやってます。

 前回の続きです。あらかじめ「名声獲得倍率」シートの追加をしておいてください。

はじめに

 灰桜色の書式を背景に靡かせ、関数でシートに書かれている記号があります。このお人形のように綺麗で可愛らしく、夏の太陽だってもっと熱く燃えてしまいそうな記号は一体何でしょう。そう、「?」です。

やりたいこと

 投げるプロヴィンスを決めて書くとこまでは終わったけど、何戦あるか判断して?とか書いていくのめんどい……自動化しよ……
 こんな感じです。

f:id:TS_hartmann:20201217142621p:plain

予定表(右側の表)記入自動化の手順

手順1. GAS側の記述

 いつも通り先にコードを書きます。

function memo1() {


  //タイムスケジュール情報取り込み     
       
  var id = "スプレッドシートのid";  
  var spreadSheet = SpreadsheetApp.openById(id);  
  var sheetName = "戦闘予定(CWE用)";
  var sheetName2 = "対戦相手判別用";
  var sheetName3 = "名声獲得倍率";
  var sh = spreadSheet.getSheetByName(sheetName); //記入セルを判断するため
  
  var data1 = sh.getRange("F11:F109").getValues(); //縦軸
  var result1 = data1.filter(function(value){return (value[0].length > 0)}); //空白要素の配列を削除
  var Arr = Array.prototype.concat.apply([],result1); //2次元配列を1次元配列にする
  var count = Arr.length;
  
  
  var sh2 = spreadSheet.getSheetByName(sheetName2); //
  var count2 = sh.getDataRange().getLastColumn()-15;//最終列-15
  
  var data2 = sh.getRange("P9:BC9").getValues(); //横軸
  var result2 = data2.filter(function(value){return (value[0].length > 0)}); //空白要素の配列を削除
  var Arr2 = Array.prototype.concat.apply([],result2); //2次元配列を1次元配列にする
  var count3 = Arr2.length;
  
  var data3 = spreadSheet.getSheetByName(sheetName3).getRange("N11:N109").getValues(); //最大参加可能クラン数
  var Arr3 = Array.prototype.concat.apply([],data3); //2次元配列を1次元配列にする
  
  var data4 = sh.getRange("G11:G109").getValues(); //戦線名
  var result4 = data4.filter(function(value){return (value[0].length > 0)}); //空白要素の配列を削除
  var Arr4 = Array.prototype.concat.apply([],result4); //2次元配列を1次元配列にする
  
  var data4 = sh.getRange("L11:L109").getValues(); //戦闘方式
  var result4 = data4.filter(function(value){return (value[0].length > 0)}); //空白要素の配列を削除
  var Arr4 = Array.prototype.concat.apply([],result4); //2次元配列を1次元配列にする
  
  

  
  //予定表に@を書き込む
  
  var lang1 = "@"
  var lang2 = "?"
  
  var colnum = [];//列記入用空白定義
  var debug = []; //デバッグ用空白定義
  for(var i = 0; i < count; i++){
  
     if(4 < Arr3[i] && Arr3[i] <= 8){
       var count4 = 3;
       }
     if(8 < Arr3[i] && Arr3[i] <= 16){
       var count4 = 4;
       }
     if(16 < Arr3[i] && Arr3[i] <= 32){
       var count4 = 5;
       }
     if(32 < Arr3[i] && Arr3[i] <= 64){
       var count4 = 6;      
       }
     debug.push(count4)
  
     for(var j = 0; j < count3; j++){  
         if(Arr2[j] === Arr[i]){ //列番号検索※HLOOKUP関数みたいな働き
            var col2 = (j+16);           
            colnum.push(col2);
            spreadSheet.getSheetByName(sheetName3).getRange(i+11,15).setValue(col2 + (count4-1)*2); //領地戦列を記入
            
            for(var k = 0; k < count4; k++){
               if(k < 1 ){
                 sh.getRange(i+11,col2 + k*2).setValue(lang1); //該当セルより同じ列で右側にあるセルに@と入力する
               }else{
                    sh.getRange(i+11,col2 + k*2).setValue(lang2); //該当セルより同じ列で右側にあるセルに?と入力する
                    }
            }   
         }
      }
  }
 
   Logger.log(Arr4);
}

 これを実行するだけです。ボタンを押したらこれが実行されるようにしておきましょう。

手順2. スプレッドシート側の記述

 ありません。追加するシートもないです。
 これで下の画像のようになると思います。

f:id:TS_hartmann:20201217143836p:plain
こんな感じ

いつもの(見ない方がいいかも……)

はんどさん「じゃああんたを引き渡して報酬をもらうとするよ」
46猫「報酬がもらえて嬉しい?嬉しい?その喜んでいる顔もっと見せて!!ハァ…ハァ...ハァ…」
はんどさん「うえぇ……(ドン引き)」
私「メイン指揮官じゃないですかぁ!」
はんどさん「なんだお前いたのか」
私「今来たばっかりですよ。ちょっと戦闘管理に困りまして、はんどさんを頼ろうかと……」
46猫「ハァ…ハァ…ハァ…」
私「この人、何をしたんですか?妙に目を輝かせて私を見てますけど……」
46猫「かわいい……怒った顔はもっとかわいいに違いないわ!!」
はんどさん「こいつかぁ?あーえっとなぁ……おっ!(ニヤリ)」
はんどさん「実はなあ……シートを壊して回っているシート壊し魔なんだよ。勝手にいじるくらい凶悪なやつでなあ」
私「はえ、勝手にいじる……ですか……」
はんどさん「CWEの時に使うシートで色んな関数が書いてあるシートだったなあ!」
私「!!」
46猫「(目を輝かせる)」
私「CWE……色んな関数……」
46猫「あなたが誰だか、何だか良くわからないけど、怒っていることだけは分かるわ」
私「……詳しく聞かせてもらえますか(手を握る)」
46猫「んんああっ!(興奮)その怒った顔良いわぁ!最高……んああっ!(絶頂)」


※半分実話です

ts-hartmann.hatenablog.com