【Arduino自動化07】トーナメント自動周回【マクロコン代替】

ますたーです。こんにちは。

今回は、「トーナメント」を自動周回するArduino Leonardo自動化の記事です。前回の記事に引き続き、マクロ対応コントローラーで自動化していたコマンドをArduino Leonardoで実装します。今回はJoy-Conのスティック入力操作にもチャレンジします。ゆえに、これを読むとArduino Leonardoでマクロ対応コントローラーの「完全代替」ができるかもしれません。

なお、Arduino Leonardo自動化の導入・機材構成については導入記事を参考にしてください。

導入記事:【Arduino自動化01】Arduino開発環境の導入

※本ブログに初めてお越しの方は「本ブログについて」もぜひ、ご覧ください。

 

概要

本記事では、シュートシティの「トーナメント」自動周回を実装します。

f:id:tangential_star:20201213004402p:plain
f:id:tangential_star:20201213004416g:plain
トーナメントを自動周回(1周8分ほど)。各種ガンテツボールやかえんだまが手に入る。

 

今回の目次です。

 

作ったきっかけ

実は、私もマクロ対応コントローラーは持っています。剣盾発売当初から今年の2月まではずっと愛用していたのですが、ずっと下記4点をネックに感じていました。

  1. 方向キーを輪ゴムなどでアナログに固定しなければならない(安定しない)
  2. 無線コントローラーだと充電しないと使えない(使い続けられない)
  3. やることを変えるたびにマクロ登録をし直さないといけない(面倒くさい)
  4. Homeボタンがマクロに登録できない(設定画面に入れない)

Arduino Leonardoは、これらを全てを解決できるからという判断で購入し、今に至るわけです。結果、良かったと思っています(2台買いました)。

f:id:tangential_star:20201213004749j:plain
f:id:tangential_star:20201213004738j:plain
マクロ対応コントローラーを使っていたが、結局Arduino Leonardoに落ち着いた。

 

この記事のスタンス

前回の記事では、「決められたコマンド入力」が分かっているものをArduino Leonardoで動く自動化プログラムを作りましたが、今回はその改良版で、Joy-Conのスティック入力にも対応させます。

したがって、もし読者の皆様にて「入力したいキーの順番があらかじめ決まっている」ものがあれば、スティック入力を含めてプログラムを自分で書ける雛形としてご利用いただけると思います(他のサイトで紹介されているマクロコントローラーの自動化コマンドなど)。ソースコードだけ欲しいという方はどうぞ⇒ 読み飛ばす

 

トーナメント自動周回のやり方

様々なブログでも紹介されている、かなり有名でオールドスタイルなやり方ですが、努力値AS振りザシアンLv.100@くちたけん(わざ:アイアンヘッドのみ;PP最大値まで増やしておく)1匹のみを手持ちに準備し、コマンドを入力し続けるだけです。ザシアンがいない人は、Lv.100サザンドラ@こだわりメガネ(わざ:あくのはどうのみ)でも多少効率が落ちますがOKです。

f:id:tangential_star:20201213004956p:plain
f:id:tangential_star:20201213005002p:plain
手持ち最適解は「アイアンヘッド」のみを覚えた AS極振りザシアン@くちたけん 1匹。

肝心の入力コマンドですが、基本的に「A連打、ちょっとだけB」というすごくシンプルな入力です。ただし、スティック上方向を起点(0度)として、時計回りにおおよそ15度~22度の向きに傾ける必要があり、この角度が手動だとかなりシビアです。ズレるとキチンと再戦ができず、ループが途切れてしまいます。

輪ゴムやテープなどでアナログに固定しなければならないマクロ対応コントローラーでは多少ツライですが、この点、Arduino Leonardoであればスティック入力のズレが起こることなく確実に再戦できます。

f:id:tangential_star:20201213012104p:plain
f:id:tangential_star:20201213013140g:plain
スティックを真上から約15~22度の方向に倒すことで、賞品受け取り後に再戦できる


Arduinoでの自動化の実装

上述の通り、今回のコマンドはかなりシンプルで、スティックを倒しながらA連打+Bを1回というものです。プログラムに起こすと、下記の様になります。

こちらは説明用に抜粋したプログラムの一部です。⇒ 完成版プログラムまで読み飛ばす

#define HOLDTIME (95) // 1回のキー入力の長押し時間

void loop() {
  // ↓ここに繰り返すコマンド列を入れよう
  TiltLeftStick(17, 1.0, 0, 0); // 17度の方向・強さMAX・傾けっぱなし・待機時間0
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("B",    HOLDTIME, 150); // B HOLTIMEミリ秒長押し 0.15秒待機

  // ↑ここに繰り返すコマンド列を入れよう
}

今回は「スティックを右上に傾ける」「Aボタンを9回押す」「Bボタンを1回押す」コマンドなので、書き換えはわずか11行で完成します。簡単ですね。

構成としては、前回の記事と同様、「loop(){」とそれに対応する「}」の間に自動化するコマンドを1行ずつ書き込むだけの簡単設計です。新たに「TiltLeftStick()」という関数を実装したので、スティック入力の自動化にも対応しています

 

  TiltLeftStick(17, 1.0, 0, 0); // 17°の向き、傾きMAXで倒し続ける。

関数の仕様の話をしてもしょうがないですが、要するにJoy-Con左スティック入力は「どの向き」に「どのくらい倒すか」を指定することで実現できると理解いただければOKです。また、「倒し続ける時間」「スティックを戻した後の待機時間」はそれぞれ「ミリ秒」で指定できます。

なお、倒し続ける時間・待機時間(3つ目・4つ目)をそれぞれ「0」「0」と指定すると倒し続ける(スティック位置を戻さない)こともできます。

上記例だと17°の方向に傾きMAXでスティックを倒し続けるようになっています。

f:id:tangential_star:20201213101728p:plain

実装した「TiltLeftStick()」関数は極座標系のイメージ。倒す向きと強さをそれぞれ指定する

 

プログラムの使い方(準備)

前述の通り、手持ちをLv100ザシアン@くちたけん 1匹のみにして、シュートシティの右上の方にあるポケモンリーグ本部へ行き、受付の前でArduino Leonardoを差し込むだけです。

f:id:tangential_star:20201213113132p:plain
f:id:tangential_star:20201213113250p:plain
f:id:tangential_star:20201213113200p:plain
f:id:tangential_star:20201213113241p:plain
手持ちはザシアン1匹。シュートシティのポケモンリーグ本部の受付前でArduinoを差し込む

 

プログラムのソースコード

今回のプログラムは前回の記事に機能を追加したものです。読者の皆様が流用して1から使いやすいように、さらにキレイに作っています。

今回はJoy-Con左スティック入力にも対応したので、このソースコードを全文コピペして、loop()の中身だけ好みのものに変更すれば、オリジナルのマクロコントローラーとしても使うことができると思います。興味があればチャレンジしてみてください。

 

/* 
 *  トーナメント自動周回
*/

/* マクロコンのコマンドをソースコード化するための雛形【Newバージョン】

loop(){」のすぐ下に繰り返したいコマンドを書いていくだけで、
Arduino Leonardo用のソースコードができあがる簡単設計。

書き方としては1行ずつ↓を書いていく。
・ボタン入力:「PushKey("入力キー", 長押しミリ秒, 待機ミリ秒 ); 」または
・スティック:「TiltLeftStick(真上を基準に傾ける角度(0-360),傾きの強さ(0.0-1.0),長押しミリ秒, 待機ミリ秒)」
 ※スティック入力に関しては、長押しミリ秒を「0」にすると傾けっぱなしになります。

たとえば、「Aボタンを1.5秒間押してその後3秒待機する」場合には、
  PushKey( "A" , 1500 , 3000 );
と書けばオーケー。
たとえば、「スティックを3秒間右に傾けてその後2秒待機する」場合には、
  TiltLeftStick(90, 1.0, 3000, 2000);
と書けばオーケー。

なお、プログラムはすべて「半角英数字」で書くこと!

ちなみに「//」の右側の文字はプログラム上で無視されるので開発用コメントを残せる。
*/
#include <SwitchControlLibrary.h>

#define HOLDTIME (95) // 1回のキー入力の長押し時間

void PushRL(int delay_time_ms);
int PushKey(char* keyname, int holdtime, int delaytime);
void TiltLeftStick(int direction_deg, double power, int holdtime, int delaytime);

void setup() {
  // コントローラーとして認識されるためにRLを7回ほどカチャカチャする
  for(int i=0;i<7;i++)PushRL(300);
  delay(1000);
}

void loop() {
  // ↓ここに繰り返すコマンド列を入れよう
  TiltLeftStick(17, 1.0, 0, 0); // 17度の方向・強さMAX・傾けっぱなし・待機時間0
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("A",    HOLDTIME, 150); // A HOLTIMEミリ秒長押し 0.15秒待機
  PushKey("B",    HOLDTIME, 150); // B HOLTIMEミリ秒長押し 0.15秒待機

  // ↑ここに繰り返すコマンド列を入れよう
}

void PushRL(int delay_time_ms){
  SwitchControlLibrary().PressButtonR();
  SwitchControlLibrary().PressButtonL();
  delay(HOLDTIME);
  SwitchControlLibrary().ReleaseButtonR();
  SwitchControlLibrary().ReleaseButtonL();
  delay(delay_time_ms);
  return;
}
int PushKey(char* keyname, int holdtime, int delaytime){
  // ホームボタン・方向キーはRight, Left, Up, Down, Homeなど2文字以上で入力。
  // その他ボタン入力は1文字(A,B,X,Y,R,L,+,-)ZR・ZLにも対応
  // 同時押しは非対応
  
  if(strlen(keyname)==1){
    switch(keyname[0]){
      case 'A': case 'a': // A
        SwitchControlLibrary().PressButtonA(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonA(); delay(delaytime);
      break;
      case 'B': case 'b': // B
        SwitchControlLibrary().PressButtonB(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonB(); delay(delaytime);
      break;
      case 'X': case 'x': // X
        SwitchControlLibrary().PressButtonX(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonX(); delay(delaytime);
      break;
      case 'Y': case 'y': // Y
        SwitchControlLibrary().PressButtonY(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonY(); delay(delaytime);
      break;
      case 'L': case 'l': // L
        SwitchControlLibrary().PressButtonL(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonL(); delay(delaytime);
      break;
      case 'R': case 'r': // R
        SwitchControlLibrary().PressButtonR(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonR(); delay(delaytime);
      break;
      case 'H': case 'h': // Home
        SwitchControlLibrary().PressButtonHome(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
      break;
      case '+': case 'p': case 'P': // Plus
        SwitchControlLibrary().PressButtonPlus(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonPlus(); delay(delaytime);
      break;
      case '-': case 'm': case 'M': // Minus
        SwitchControlLibrary().PressButtonMinus(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonMinus(); delay(delaytime);
      break;
      default:
      break;
    }
  }else if(strlen(keyname)>=2){
    switch(keyname[0]){
      case 'z': case 'Z': // ZR/ZL
        if(keyname[1]=='R'||keyname[1]=='r'){
          SwitchControlLibrary().PressButtonZR(); delay(holdtime);
          if(holdtime>0)SwitchControlLibrary().ReleaseButtonZR(); delay(delaytime);
        }
        if(keyname[1]=='L'||keyname[1]=='l'){
          SwitchControlLibrary().PressButtonZL(); delay(holdtime);
          if(holdtime>0)SwitchControlLibrary().ReleaseButtonZL(); delay(delaytime);
        }
      break;
      case 'r': case 'R': // right
        SwitchControlLibrary().MoveHat(2); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'l': case 'L': // left
        SwitchControlLibrary().MoveHat(6); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'u': case 'U': // up
        SwitchControlLibrary().MoveHat(0); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'd': case 'D': // down
        SwitchControlLibrary().MoveHat(4); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'H': case 'h': // Home
        SwitchControlLibrary().PressButtonHome(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
      default:
      break;  
    }
  }else{
    return -1;
  }
  return strlen(keyname);
}
void TiltLeftStick(int direction_deg, double power, int holdtime, int delaytime){
  double rad = (double)direction_deg*PI/180.0; // 弧度法(ラジアン)変換
  int x, y;
  x = (double)128*sin(rad)*power;
  y = (double)-128*cos(rad)*power;
  x += 128; y += 128;
  if(x >= 255) x=255; if(x <= 0) x=0;
  if(y >= 255) y=255; if(y <= 0) y=0;

  SwitchControlLibrary().MoveLeftStick(x,y);
  if(holdtime> 0){ // holdtime=0のときは押しっぱなし。
    delay(holdtime);
    SwitchControlLibrary().MoveLeftStick(128,128); // 傾きを直す
  }
  if(delaytime>0) delay(delaytime);
  return;
}

 

f:id:tangential_star:20201213004416g:plain
f:id:tangential_star:20201213112920p:plain
自動周回ではまれにガンテツボールや「かえんだま」「どくどくだま」が入手できる

 

あとがき

今回もマクロ対応コントローラーの代替としてプログラムを実装・紹介しました。新たにスティック入力に対応できるようになったことで、Arduino Leonardoがまさにマクロ対応コントローラーの「完全代替」に一歩近づいたかな、と感じています。

思えば、私が今年2020年の2月にArduinoを買ったきっかけになったのが、まさにトーナメントの自動周回でコントローラーのスティック入力がうまく固定できなかったから、だったのですよね。せっかくデジタルに入力制御ができるマクロ対応コントローラーなのに、肝心の入力がアナログだという矛盾をはらんでいて、個人的に矛盾だらけだなぁと思ったものです。

ゲーム中でも、スティック入力操作を使う動作は沢山ありますし、剣盾なら「カレーづくり」や「マホミルの進化(リザードンポーズ)」、なによりも「町中の移動」や「タマゴ孵化」などでしょうか。今回の雛形を使えば、これらの自動化もおいおい実装できるようになるかもしれませんね。

 

なにはともあれ、皆様のポケモン自動化ライフが豊かになることを願っています。

 

ではではc⌒っ.ω.)っ

 

前回記事:

【Arduino自動化06】完全放置「マックスこうせき」集め【ダイマックスアドベンチャー】

次の記事:

【Arduino自動化08】バトルタワー自動周回【BP稼ぎ】 

導入記事:

【Arduino自動化01】Arduino開発環境の導入