【Arduino自動化20】Switch2台で「完全」自動!レイドバトル自動周回【デリバード・色違いレイドなど】

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

今回はSwitchを2台使った、「完全自動」レイドバトル自動周回の記事です。

本ブログのArduino自動化記事記念すべき「第20回」に相応しい内容ですね。YouTubeにも動画をアップしました!

 

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

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

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

 

概要

Nintendo SwitchArduino Leonardoを2組を使い、片方でレイド募集、もう片方でレイド周回を行います。おそらく、完全自動で同一レイド周回ができる最適解です。

※1人(1台)で周回する場合は第19回の記事、ローカル配布だけを行いたい場合は第18回の記事をご覧ください
※本記事は、2組の機材が必要です。Arduinoの接続にはジャンパーケーブル(オス-オス)が3本必要です(後述)

f:id:tangential_star:20211009123347p:plain
f:id:tangential_star:20211009122137g:plain
Switch2台でレイドバトルを「完全」自動で周回。レイド報酬稼ぎや乱数色違いの捕獲が自動化できる!

 

もくじです。

ソースコードだけ欲しい、という方はどうぞ→読み飛ばす

概要だけを知りたい方は⇒YouTube動画をご覧ください。

【注1】今回の自動化はArduino Leonardoを2組使います。また、今までの記事よりも機材構成・接続の難易度が高いです(といっても繋ぐだけですけど)。機材の接続手順が非常に大事ですので、今回はできる限り読み飛ばさず読んでいただいたほうが良いかもしれません。
【注2】本記事では、シリアル通信を使います。筆者自身、このあたり専門家では無いのでうまく行かなくてもご了承ください><
【注3】本記事では「Arduino Leonardo」は純正かつ「ソケットヘッダ付」のものを利用している想定で書いています。特に互換機など、ご自身がお持ちのボードに応じて、適宜読み替え・ソースコード修正が必要になる場合があります。なお、互換機の中には、シリアル通信ができないボードも一部あります。

 

YouTubeにも動画をアップしました!

本ブログの内容は、YouTubeにもアップロードしています。合わせてご視聴いただけると、より理解が深まると思います。

★2021/10/11 AM7:00 公開!

 

0.できたこと(まず見てほしい)

結論ファーストです。「Arduino Leonardo」2台を有線接続し、それぞれがNintendo Switchを操作することで、レイドバトルの自動周回を、「完全放置」でできるようになりました。結果、デリバードレイドを一晩中自動周回させ、マックスこうせき・けいけんアメがカンストしました

本記事では、必要機材・やり方(配線方法)・ソースコードをすべて紹介します。

f:id:tangential_star:20211009161947j:plain
f:id:tangential_star:20211009181507p:plain
自動でレイド周回させれば、育成アイテム(けいけんアメ)のカンストも夢じゃない

1.前提知識

ここは、開発経緯や、前提になる知識について余談的に書いています。

お急ぎの方は⇒2章「事前準備」まで読み飛ばす

開発の背景:いままでのレイドバトル自動化の課題

本ブログでは、レイドバトルの周回作業の簡略化・省力化を目的とした記事をいくつか執筆してきました。例えば、前々回(第18回)の記事ではレイドバトルのローカル配信(=募集側)の自動化、前回(第19回)の記事ではソロ・レイド周回(=セルフ周回)の自動化を、それぞれ実装しました。

しかしこれらは、完全に独立したプログラムであり、「募集側と周回側を一緒に動かす」ことはできませんでした。言い換えれば、目的のレイドバトルを周回するためには、何らかの「手動作業」が必要だった*1のです。

個別には非常に便利な自動化でした(私もしばらく愛用していました)が、こと「放置」という観点からは、もう一歩踏み込みたい課題が残っていました。そこで、今回は「募集側」「参加側」を両方自動化しよう!と考えました

f:id:tangential_star:20210503200445g:plain
f:id:tangential_star:20210919200733g:plain
今まで「レイド配布」「ソロレイド周回」はあったが、2台で連動するものはなかった
(左:第18回 ローカル配布/右:第19回 ソロ・レイド周回)

Arduino2台を通信させて、レイドバトル「完全」自動周回を実現!

レイドバトルの「募集側」「参加側」の両方を自動化するにあたって、2台の操作が「ちゃんとタイミングよく動いてくれる」ことが前提となります。ところが、Arduinoはマクロコントローラーと同様、単体では「●●秒待って、●●を押して…」と、単純な操作の自動化しかできません

要するに、きっちり時間を測って「募集側」「周回側」の操作を独立してプログラムを書いても、どうしてもだんだんと動作がズレてしまい、長時間の放置はできなくなってしまいます。

そこで、2台のArduinoの同期を取るために「シリアル通信*2」を利用しました。つまり、物理的に2台のArduino Leonardoを接続し、募集側の状況をもう1台のArduinoに報告するようにしたのです。

f:id:tangential_star:20211009142223p:plain
f:id:tangential_star:20211009142217p:plain
「シリアル通信」有無の比較のイメージ図。
要するに、2台のArduinoが会話しながら作業を自動化するのでズレないのだ!

より具体的には、募集側Arduinoから「今からレイドバトルを始めるよ!」とか「今から戦闘だからA連打してね!」といった状況を、参加(周回)側のArduinoにシリアル通信で送信するようにプログラムを書きました。

これにより、募集側と周回側のArduinoそれぞれの操作タイミングがズレずに、完全自動でのレイドバトル周回が実現できました。

通信のためには、Arduino2台の配線作業が必要

さて、今回はArduino2台の操作タイミングをあわせるために「シリアル通信」を採用することにしました。

この方式では、前述のとおり、物理的に2台のArduinoを接続するので、接続用の銅線*3がいります。さらに、Arduino Leonardoにはシリアル通信できるPinとできないPinが混在します(要するに、挿すところを間違えたら動きません!)。

f:id:tangential_star:20211009153416j:plain
f:id:tangential_star:20211009153354j:plain
Arduino同士を接続するためには「ジャンパーワイヤー(接続用の銅線)」が必要
Arduinoのpinがソケットならオス端子、ピンヘッダならメス端子が必要だ

そういう意味では、「シリアル通信」を使うためには物理的な配線作業が必要になるため、今までよりもちょっとだけ難易度が上がります。とはいえ、冒頭にも書いたとおり、原則として「差し込むだけ」ですので、頑張りましょう。詳しくは、次章で紹介します。

 

2.事前準備(プログラム書き込みと機材の接続)

配線に必要なもの(ハードウェア)

今回の自動化において、準備するものは下記の通りです。ジャンパーワイヤー以外は【番外編3】筆者の使用機材に購入リンクを含め載せています。

  • 【必須】Nintendo Switch(またはLite)2
  • 【必須】Arduino Leonardo 2
  • 【必須】SwitchとArduinoを繋ぐUSBケーブル(兼 PCからの書き込み用)2
  • 【新たに必要】ジャンパーワイヤー(オス-オス) 3
  • (あれば便利)Switchドック(またはハブスタンド)&純正充電アダプタ 2
  • (あれば便利)スイッチ付きUSBコネクタ 1

要するに、【Arduino自動化01】導入記事に書いた基本構成が2組+ジャンパーワイヤー3本があれば最低限OKです。

f:id:tangential_star:20211009163922p:plain

今回のレイドバトル自動化で使う機材の一覧(配線イメージ)
慣れるまでは、とりあえず画像保存して適宜確認すると良い。

お使いのArduino Leonardoが、【番外編3】筆者の使用機材で紹介している、純正のものであればソケットヘッダが付いているはずなので、ジャンパーワイヤーはオス-オスでOKです。

なお、ジャンパーワイヤーとは、つまるところただの銅線(=抵抗が無い)ですから、よほどのことがなければ、どこのメーカーから購入してもOKです。筆者はエントリーキットに入っていたものを使っています。
 → 持っていない人は「Arduino ジャンパーワイヤー オス オス」でAmazon検索

ただし、pinの形状すなわち、オス-オス、オス-メス、メス-メスが間違っていないかは注意して購入しましょう*4。今回使うのはわずか3本ですし、大量に買っても余らせがちなので、無駄遣いにならないように選びましょう(たぶん、本ブログでジャンパーワイヤーを使うことも今後は無いでしょうし)

f:id:tangential_star:20211009160827j:plain
f:id:tangential_star:20211009160757j:plain
左写真のようなソケットヘッダ付のArduino Leonardoなら、ジャンパーワイヤーは「オス-オス」でOK
※右写真の「メス」のジャンパーワイヤーと間違えないように注意!

 

プログラムを書き込んで配線(Switchを接続する前の作業)

手順1.2台のArduinoにそれぞれプログラムを書き込む

まずは、2台のArduino Leonardoそれぞれに後述の「ホスト側(配布側)」「参加側(周回側)」のプログラムをIDEで書き込みます。

ここの手順は普段となんら変わりありませんが、このとき、ArduinoはPCとのみ接続し、Arduino同士の配線は後回しにしてください(重要)Arduino同士を接続した状態でプログラムを書き込むと、場合によっては干渉を起こし、正しくプログラムが書き込まれない可能性があります。

また、1度にPCに接続するArduinoも1台にしておいた方が、何かと安定します。なお、どちらのArduinoにどちらのプログラムを書き込んだか、しっかりわかるようにしておきましょう。

f:id:tangential_star:20211009175616p:plain

まずはプログラムをArduinoに書き込もう。PCと同時に接続するArduinoは1台だと安定する

 

手順2.Arduino2台を接続(配線)する

プログラムをそれぞれのArduinoに書き込んだら、パソコンからArduinoを取り外してください。その後、ArduinoをSwitchに接続する前(重要)に、Arduino同士の配線を済ませます。具体的には、レイド募集側の11番ピンともう片方の9番ピンを、レイド募集側の10番ピンともう片方の10番ピンを、そして、レイド募集側の5Vピンともう片方の5Vピンを、それぞれ接続します。

なお、接続する順番や、ジャンパーワイヤーの色は気にしなくてOKです。コツとしては、しっかり差し込むことでしょうか。あくまでもアナログ的に接続するので、うまく動かない場合は接続不良や、ケーブルの断線も疑うようにしましょう

ちなみに、シリアル通信では、本来「GND」にもワイヤーの接続が必要らしいです。もし万が一、上記手順でうまく行かないor動作が安定しない場合は、更に1本追加して、募集側・参加側の両方のGND端子同士を繋ぐジャンパーワイヤーを接続してください。

f:id:tangential_star:20211010132220p:plain

両方のArduinoにプログラムを書き込んだら、PCから外して3本のジャンパーケーブルを接続しよう。

 

手順3.必要に応じてハブスタンドほかに接続しておく

あとは、Nintendo Switch以外の配線類をすべて済ませておきます。充電アダプタも、つけるのであれば、このタイミングで配線に加えておいてください*5

なお、配線が完了しても、Nintendo Switchはまだ差し込まないでください。

f:id:tangential_star:20211009183304p:plain

Arduinoの配線が終わったら、充電アダプタ・スタンドなどの配線も済ませよう。
この状態になれば、機材側は準備OK。あとはポケモン剣盾の準備だけだ。

 

さて、以上の手順で、今回の鬼門は終わりました。お疲れさまでした。あとは、Switchを差し込むだけ、なのですが、レイド周回側のSwitchを先に差し込んで動作が止まるのを確認してから、レイド募集側を差し込む必要がある点には注意しておきましょう。詳しくは後述します。

 

3.Switch(ポケモン剣盾)の準備

レイド1ターン周回要員の育成

基本的には、【Arduino自動化18】に掲載の流れに沿って、募集側・周回側ともに1匹ずつ最適なポケモンを手持ち先頭に加えておきましょう。なお、自動化の特性上、使える「わざ」は1つだけにしておき、火力強化系のアイテムをもたせておくと安心です。

自動下記に引用(一部改変)しますが、デリバードレイドの周回を行う場合には、CS極振りレジエレキと、AS極振りルガルガン(まよなかのすがた)がオススメです。

初撃オススメ:Cぶっぱレジエレキ「エレキボール」

レジエレキはS種族値200というトンデモ性能で、デオキシススピードフォルムすら抜き去る圧倒的な素早さの持ち主。そのユニークな特性「トランジスタ」により、でんきタイプのわざであれば実数値1.5倍の火力を出せるため、初撃に使うにはもってこいです。

f:id:tangential_star:20210504164042g:plain
f:id:tangential_star:20210504164006j:plain
初撃オススメはCぶっぱレジエレキによる圧倒的素早さからのエレキボール
追撃オススメ:ASぶっぱ夢ルガルガン@こだわりハチマキ「ストーンエッジ

続いて、追撃要員の紹介です。こちらは、こうげき努力値に極振りし、こだわりハチマキを持たせたルガルガンストーンエッジがオススメです。夢特性であれば「ノーガード」になるため命中不安なエッジを確定で当てることができます。また、いわタイプなので、デリバード(こおり・ひこう)の4倍弱点を突くことができることもポイントです。無論、天候による威力増減の影響も受けません。

f:id:tangential_star:20210504164026g:plain
f:id:tangential_star:20210504164011p:plain
追撃オススメはASぶっぱルガルガン夢特性:ノーガード)のストーンエッジ

これらのポケモンを準備したら、配信側・参加側の2プレイヤーがそれぞれ1匹ずつを持つように手持ちの先頭にあらかじめ加えておきましょう

 

レイド募集側の準備:デリバード★4のレイドバトルの巣穴の目の前でセーブ

ホスト側(募集側)では、クライアント側(周回側)で周回させたいレイドバトルを出現させた状態にしておきます。詳細説明は【Arduino自動化18】に譲りますが、デリバードレイドは最高効率のレイド報酬と言われていますので、今回は、デリバードレイド(★4)バトルを想定しています。

ホスト側の事前の準備は下記2つです。

  1. 「ねがいのかたまり」を入れて厳選済の巣穴の目の前でセーブ
  2. 手持ち先頭を1ターン周回要員にしておく

デリバードレイドでは、カンムリ神殿の巣穴が好アクセスポイントなので、うまく活用すると良いでしょう。

f:id:tangential_star:20210504173750g:plain
f:id:tangential_star:20210504173019g:plain
デリバードレイド探しは、アクセス良好なカンムリ神殿の巣穴がオススメ。
募集側では、目的のレイドバトルを厳選済の巣穴の目の前でセーブしておこう。


レイド周回側の準備:目の前に話しかけるものが無いところでセーブ

続いて、クライアント側(レイド周回側)の準備です。準備、と言っても、守るべきことは3つだけ。

  1. 十分な量のモンスターボールを所持し、リュック先頭に並び替えておくこと
  2. 目の前に話しかけるものが一切無い場所でセーブすること
  3. 手持ち先頭を1ターン周回要員にしておく

特に、2はループそのものが途切れる要員となりますので、周りから野生のポケモンなどが突進してきたり、ワイルドエリアで手持ちポケモンに話しかける方向を向いていたりなどは避けてください。

f:id:tangential_star:20211010004642p:plain
f:id:tangential_star:20211010004614p:plain
周回側は、大量のボールを持って、目の前になにもないところでセーブするだけだ

 

4.SwitchをArduinoに接続する

ここまで、準備お疲れ様でした。

あとは、Switchを差し込んで周回していくだけです。ただし、2台のSwitchは同時ではなく、時間を空けて、順番に差し込む必要があります。具体的には、先に「レイド参加側」のSwitchを接続し、Homeボタンが2回押されたような挙動を確認した上で、もう一台(募集側)のSwitchを接続する、という流れです。

このあたりは、スイッチ付USBアダプタを機材に入れているかどうかで煩雑さが変わるので、順番に説明します(無くても大丈夫ですのでご安心を)。

USBスイッチ付アダプタがある場合

接続の手順は以下の通りです。

  1. 周回側・募集側のNintendo SwitchをそれぞれのArduinoに接続する
  2. 周回側のArduinoのLEDが消灯するまで待つ
  3. USBスイッチを「ON」にする

f:id:tangential_star:20211010114939g:plain

実機でのイメージ。Switchを接続したらLED消灯を待ち、USBスイッチをONにする。

f:id:tangential_star:20211010111312p:plain

両方のSwitchを接続して、周回側のLED消灯後にUSBアダプタをONにすればOK

 

USBスイッチ付アダプタが無い場合

接続の手順は以下の通りです。

  1. 周回側のNintendo Switchを周回側のArduinoに接続する
  2. 周回側のArduinoのLEDが消灯するまで待つ
  3. 募集側のNintendo Switchを募集側のArduinoに接続する

f:id:tangential_star:20211010111713p:plain

まず周回側のSwitchを接続してLEDの消灯を待ってから、募集側のSwitchを挿せばOK

 

5.ソースコード

今回は、レイド自動周回「募集側(ホスト側)」「周回側(クライアント側)」の2つのソースコードを公開します。配線・接続手順については前述の通りです。

一応、ソースコード冒頭にもコメント部分に記載していますが、よくわからない人は、適宜本記事を読み直していただければと思います。

5-1.レイドバトル募集側(ホスト側)

レイド募集側のプログラムです。

/*  ★★★★★★★★★★★★★★★★★★★★★★★★★★★★
 *  ★2台で自動 デリバード レイド周回【ホスト側(送信側)】★
 *  ★★★★★★★★★★★★★★★★★★★★★★★★★★★★
 *  
 *  デリバードレイドの周回作業を自動で行ってくれるすごいやつ。
 *  
 *  ★デリバード星3または星4の巣穴を厳選しておきましょう
 *  ★巣穴の前でセーブの上、少なくともボールを1つ以上持っておきましょう
 *  
 *  【利用方法】
 *  注意:こちらのArduinoとSwitchとの接続は、手順の最後です!!!
 *  
 *  先に、2台のArduinoを配線してください。
 *  使うpinは、本ArduinoではRX, TXとしてそれぞれ10, 11を使います。
 *  すなわち、こちらのRX(10)を、ホスト側のTX(10pin)に接続し、
 *  こちらのTX(11)を、ホスト側のRX(9pin)に接続してください。
 *  配線したら、周回側のSwitchと周回側のArduinoを接続し、動作を確認します。
 *  
 *  周回側(受信側)のSwitchが、待機ループの中に入ったことを確認の上、
 *  配線後のホスト側Arduinoをホスト側Switchに差し込んでください。
 *  
 *  ※シリアル通信を使います。Arduino同士の配線が必要です。
 *  ※よくわからない人は、ご利用をお控えください(誤って破損する可能性もあります)。
 *  
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/
#include <SwitchControlLibrary.h> // Switch自動化用のライブラリ

#include <SoftwareSerial.h> // 2台目のArduinoと通信するためのライブラリ
SoftwareSerial mySerial(10, 11); // RX, TX(pin10, 11を使います)


// うまく行かなかったら、下記いずれかで適宜入れ替えてみてください。
// 特に、Leonardoの互換ボード利用者はこの辺を触る必要性があるかもしれません。
// ※配線もTX, RXを0,1に変える必要があります【上級者向け】

#define SOFTWARE_SERIAL (1)
// #define USE_NORMAL_SERIAL (1)
// #define USE_SERIAL_1 (1)


#define HOLDTIME (95) // 1回のキー入力の長押し時間[ms]
#define WAIT_TIME (30)   // 「みんなで挑戦!」を押してから何秒待ち続けるか?
#define BATTLE_TIME (60)  // 挑戦後、何秒間レイドバトルを継続するか?[ここは60でのみ動作を確認してます]


// 通信で使う定数です。各状態に遷移時にこれらを2台目にシリアル通信で送信します。
enum {RECEIVE_BEGIN_PACKET=3, BEGIN_LOOP, START_YYCOMM, START_BATTLE, RESET_GAME } state;
#define BAND (300) // シリアルポートに接続するレートです。300[bps]で通信します。
int LED = 13; // ArduinoのLEDを簡易にデバッグ用に使います。Arduino LeonardoのLED pin番号を入力!


void PushRL(int delay_time_ms);
int PushKey(char* keyname, int holdtime, int delaytime);
void DoubleHome(void);

void setup() {
  unsigned long int current_time=0;
  unsigned long int start_time=0;

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

  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH); // LED 点灯
  DoubleHome();
  delay(5000); // 5秒待機

#ifdef USE_SERIAL_1
  // Serial1
  Serial1.begin(BAND); // ソフトウェアシリアルの初期化
  start_time=millis();
  current_time=start_time;
  while (!Serial1) {
    if(current_time - start_time >= 2000UL){
      Serial1.begin(BAND); // 2秒ごとに再試行
      start_time=millis();
    }
  }
  DoubleHome();

#endif

#ifdef USE_NORMAL_SERIAL
  // Serial
  Serial.begin(BAND);
  start_time=millis();
  current_time=start_time;
  while (!Serial) {
    if(current_time - start_time >= 2000UL){
      Serial.begin(BAND); // 2秒ごとに再試行
      start_time=millis();
    }
  }
  DoubleHome();
#endif
  

#ifdef SOFTWARE_SERIAL
  /*while(!mySerial)*/
  mySerial.begin(BAND);
  DoubleHome();
  mySerial.write(RECEIVE_BEGIN_PACKET);
  mySerial.flush();
#endif

  
  delay(3000); // 3秒待機

}


void loop() {
  unsigned long int current_time=0;
  unsigned long int start_time=0;
  unsigned long int temp_time=0;

  // ★もう1台のArduinoに状態を通知
#ifdef USE_NORMAL_SERIAL
  Serial.print(BEGIN_LOOP);
  Serial.flush();
#endif
#ifdef USE_SERIAL_1
  Serial1.print(BEGIN_LOOP);
  Serial1.flush();
#endif

#ifdef SOFTWARE_SERIAL
  mySerial.print(BEGIN_LOOP);
  //mySerial.flush();
#endif

  digitalWrite(LED, LOW);
  delay(100);
  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);  
  digitalWrite(LED, HIGH);
  delay(100);
  
  // 巣穴の前で乱数したい個体を出してセーブした状態。
  PushKey("A", HOLDTIME, 1500); // 募集画面に入る

  PushKey("A", HOLDTIME, 2500); // みんなで 挑戦!(→通信待機中 しばらくお待ち下さい)
  PushKey("UP", HOLDTIME, 300); //
  PushKey("A", HOLDTIME, 500); // 準備完了!
  PushKey("A", HOLDTIME, 500); // バトルを 開始する
  PushKey("A", HOLDTIME, 500); // 参加人数が足りません! ▼ (→サポートの トレーナーが 参加しますが よろしいですか?)

  // ★もう1台のArduinoに状態を通知
#ifdef USE_NORMAL_SERIAL
  Serial.print(START_YYCOMM);
  //Serial.flush();
#endif
#ifdef USE_SERIAL_1
  Serial1.print(START_YYCOMM);
  //Serial1.flush();
#endif
#ifdef SOFTWARE_SERIAL
  mySerial.print(START_YYCOMM);
  //mySerial.flush();
#endif

  digitalWrite(LED, LOW);
  delay(100);
  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);  
  digitalWrite(LED, HIGH);
  delay(100);
  delay(1000UL*WAIT_TIME); // WAIT_TIME秒待機する

  PushKey("A", HOLDTIME, 300); // →はい
  // ★もう1台のArduinoに状態を通知
#ifdef USE_NORMAL_SERIAL
  Serial.print(START_BATTLE);
  //Serial.flush();
#endif
#ifdef USE_SERIAL_1
  Serial1.print(START_BATTLE);
  //Serial1.flush();
#endif
#ifdef SOFTWARE_SERIAL
  mySerial.print(START_BATTLE);
  //mySerial.flush();
#endif

  digitalWrite(LED, LOW);
  delay(100);
  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);  
  digitalWrite(LED, HIGH);
  delay(100);
  
  // 挑戦画面~レイドバトル(誰かが「準備完了」を押しそこねていた場合のケアも兼ねてA連打)
  for( start_time=millis(), current_time=start_time ; current_time - start_time < (unsigned long)BATTLE_TIME*1000UL ; current_time=millis() ){
    if( current_time - start_time < (unsigned long)(BATTLE_TIME-3)*1000UL )PushKey("A", HOLDTIME, 300); // ひたすらA連打
  }

  // ★もう1台のArduinoに状態を通知
#ifdef USE_NORMAL_SERIAL
  Serial.print(RESET_GAME);
  //Serial.flush();
#endif
#ifdef USE_SERIAL_1
  Serial1.print(RESET_GAME);
  //Serial1.flush();
#endif
#ifdef SOFTWARE_SERIAL
  mySerial.print(RESET_GAME);
  //mySerial.flush();
#endif

  digitalWrite(LED, LOW);
  delay(100);
  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);  
  digitalWrite(LED, HIGH);
  delay(100);

  // リセットして次の挑戦
  PushKey("Home", HOLDTIME, 1000); // ゲーム終了のためのHomeボタン
  PushKey("X", HOLDTIME, 500);     // 終了Xを押す→終了しますか?画面
  PushKey("A", HOLDTIME, 3000);    // はい→終了しています・・・
  PushKey("A", HOLDTIME, 1000);    // ゲームタイトルクリック→ユーザを選んでください画面(複数ユーザいる場合)
  PushKey("A", HOLDTIME, 18500);   // A押してから起動までおおよそ16秒(最短目安。環境に合わせて設定すること)
  PushKey("A", HOLDTIME, 10000);   // タイトル画面からフィールド遷移までおおよそ10秒(環境に合わせて設定すること)

}

void DoubleHome(void){
  SwitchControlLibrary().PressButtonHome();
  delay(95);
  SwitchControlLibrary().ReleaseButtonHome();
  delay(300);

  SwitchControlLibrary().PressButtonHome();
  delay(95);
  SwitchControlLibrary().ReleaseButtonHome();
  delay(300);

  delay(200);
  return;
}

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);
}

 

5-2.レイドバトル周回側(クライアント側)

クライアント側のプログラムです。

/*  ★★★★★★★★★★★★★★★★★★★★★★★★★★★★
 *  ★2台で自動 デリバード レイド周回【周回側(受信側)】★
 *  ★★★★★★★★★★★★★★★★★★★★★★★★★★★★
 *  
 *  デリバードレイドの周回作業を自動で行ってくれるすごいやつ。
 *  Aを押しても大丈夫な土地で、かつ野生のポケモンと絶対エンカウントしないところでセーブしておくこと。
 *  
 *  ★十分な量のモンスターボールを準備しておきましょう。
 *  
 *  【利用方法】
 *  まずは周回側のArduinoと、ホスト側のArduinoとを、配線してください(Switchにはまだ挿さない)。
 *  
 *  使うpinは、本ArduinoではRX, TXとしてそれぞれ9, 10を使います。
 *  すなわち、こちらのRX(9)を、ホスト側のTX(11pin)に接続し、
 *  こちらのTX(10)を、ホスト側のRX(10pin)に接続してください。
 *  
 *  配線と本ArduinoとSwitchとの接続が終わったら、Home画面に入ってすぐ復帰する動作が2回、実行されます。
 *  2回実行されたら、本Arduinoは「待機状態(loop関数の中)」になっているので、この状態で、
 *  配線後のホスト側Arduinoをホスト側Switchに差し込んでください。
 *  
 *  ※シリアル通信を使います。Arduino同士の配線が必要です。
 *  ※よくわからない人は、ご利用をお控えください(誤って破損する可能性もあります)。
 *  
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h> // Switch自動化用のライブラリ

#include <SoftwareSerial.h> // 2台目のArduinoと通信するためのライブラリ
SoftwareSerial mySerial(9, 10); // RX, TX(pin9, 10を使います)

// 通信で使う定数です。各状態に遷移時にこれらを2台目にシリアル通信で送信します。
enum {RECEIVE_BEGIN_PACKET='3', BEGIN_LOOP, START_YYCOMM, START_BATTLE, RESET_GAME } state;
#define BAND (300) // シリアルポートに接続するレートです。300[bps]で通信します。
int LED = 13; // ArduinoのLEDを簡易にデバッグ用に使います。Arduino LeonardoのLED pin番号を入力!

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

#define BATTLE_TIME (100)  // [秒]
// 挑戦後、何秒間Aを連打し続けるか?(=確実に倒しきり、捕獲完了、「つぎへ」を押下、「●●をボックスへ転送しました!」までの十分に長い時間)
// 90秒だと、特性発動時に足りなかった(ヨワシのぎょぐん+捕獲成功)
// てだすけ+このゆびとまれ+捕獲成功1'35''
// 正直、ギリギリが多かったので、105とか110とかでもいいかも。

void PushRL(int delay_time_ms);
int PushKey(char* keyname, int holdtime, int delaytime);
inline void DoubleHome(void);

inline void participate(void); // call this function to participate raid battle in YY comm.
inline int A_renda_inBattle(int battle_took_time); // (while battle, arduino try push A intervally)

void setup() {
  unsigned long int current_time=0;
  unsigned long int start_time=0;

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

  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH); // LED 点灯
  DoubleHome();
  delay(5000);

  // もう一台のArduinoとの通信
  mySerial.begin(BAND); // ソフトウェアシリアルの初期化
  mySerial.listen();
  start_time=millis();
  current_time=start_time;

  DoubleHome();

}

int volatile now = '-';

// こちらは常にシリアルの受信待ち
void loop() {
  if ( mySerial.available() ) { // 受信データがあるか?
    digitalWrite(LED, HIGH);    // LED 点灯
    now = mySerial.read();      // データが無いときは呼ばれない
  }
  digitalWrite(LED, LOW); // LED 点灯
  switch( now ){
    case RECEIVE_BEGIN_PACKET:
    case 3:
      //PushKey("x",HOLDTIME, 3000);
      //while(1) delay(190);
      break;
  
    case BEGIN_LOOP:
     case 4:
      //PushKey("+",HOLDTIME, 3000);
      //while(1) delay(190);
     break;
  
    case START_YYCOMM:
    case 5:
      now = -1;
      participate();
      break;
  
    case START_BATTLE:
    case 6:
      now = -1;
      A_renda_inBattle(BATTLE_TIME);
      break;
  
    case RESET_GAME:
    case 7:
      break;
  
    default:
      break;
  }

}

void DoubleHome(void){
  SwitchControlLibrary().PressButtonHome();
  delay(95);
  SwitchControlLibrary().ReleaseButtonHome();
  delay(300);

  SwitchControlLibrary().PressButtonHome();
  delay(95);
  SwitchControlLibrary().ReleaseButtonHome();
  delay(300);

  delay(200);
  return;
}

void participate(void){
  delay(4000); // 4秒ほど待ってから…
  PushKey("Y", HOLDTIME, 1300); // YY通信の画面に入る
  mySerial.peek();
  for(int i = 0 ; i < 7 ; i++) PushKey("X", HOLDTIME, 200); // 更新作業(募集の案内を待つ)
  mySerial.peek();

  PushKey("right", HOLDTIME, 300); // レイドバトルにカーソルをあわせる
  PushKey("A", HOLDTIME, 700);     // レイドを選択
  PushKey("A", HOLDTIME, 1700);    // →参加する
  PushKey("A", HOLDTIME, 2100);    // →挑戦する (通信待機中…)
  PushKey("A", HOLDTIME, 500);     // →A(準備完了!)
  PushKey("A", HOLDTIME, 500);     // →A(準備完了!) ※予備
  PushKey("A", HOLDTIME, 500);     // →A(準備完了!) ※予備
  PushKey("A", HOLDTIME, 500);     // →A(準備完了!) ※予備
  PushKey("A", HOLDTIME, 500);     // →A(準備完了!) ※予備
  mySerial.peek();

  return;
}

int A_renda_inBattle(int RENDAJIKAN){
  unsigned long int current_time=0;
  unsigned long int start_time=0;
  unsigned long int temp_time=0;

  // 決められた時間ずっとA連打
  for( start_time=millis(), current_time=start_time ; current_time - start_time < (unsigned long)RENDAJIKAN*1000UL ; current_time=millis() ){
    if( current_time - start_time < (unsigned long)(BATTLE_TIME-3)*1000UL ){
      PushKey("A", HOLDTIME, 300); // ひたすらA連打
      mySerial.peek();
    }
  } 
  return;
}


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);
}

 

6.あとがき

まずは、本ブログ開設から11ヶ月目にしてようやく、Arduino自動化の記事「第20回」の更新を迎えることができました。まずは、本ブログを読んでいただいている皆様に、厚く御礼申し上げます。また、引き続き本ブログをよろしくお願いいたします。

 

さて、今回の記事では、前々回(第18回:レイド配布)前回(第19回:ソロレイド周回)の延長線として、諸々苦戦しながらも、ついにレイドバトルの「完全自動周回」を実現することができました。おまたせしました!!!

本プログラムを使えば、デリバードレイドの周回作業はもちろん、例えば乱数調整で出した色違いの巣穴で使えば、複数回、周回側で捕獲チャレンジを自動で行うことができます。さらに、ロトミIDくじなどで予め十分な量のマスターボールを調達しておけば、色違い量産も可能になります。本当にパワフルなプログラムとして仕上がったと思いますので、是非ともご参考いただければと思います。

 

 

本自動化については、以前より「根強い需要」が垣間見えていて、私自身も「いつか欲しいなぁ」と常日頃から感じていました。しかしながら、なかなか着手できないのには理由がありました。

実は、私自身が平日・土日も仕事に追われていることもあり、なかなか着手するモチベーションが上がらなかった上、実装のハードルがそれなりに高かったのです。今回は「シリアル通信」を使うことで2台のArduinoをタイミング同期させて動作させることができましたが、電子回路の知識が一切ない筆者にとっては「すべて手探り状態での検証・実装」でした。

そのような経緯でなかなか着手できずにいたのですが、普段より本ブログをご覧いただいている皆様からの温かいコメントや、複数人からのリクエストコメント、YouTubeチャンネルでの高評価などが増えてきており、「本当にありがたいなぁ」「リクエストにもなんとか応えてみたいなぁ」と感じていました。

そんな折、本ブログ記事も気づけば第19回まで更新されていて、次は「第20回に相応しい記事にしたいなぁ」とおぼろげながらに考え、せっかくならチャレンジしてみよう!と、1週間ほど前にようやく奮起しました。

予想以上の難しさの中、仕様検討とプログラミング、そしてトライ・アンド・エラーを経て、ついに、本記事の公開まで気合で漕ぎ着けることができました!

そんなこんなで、「おまたせしました!」「できたてほやほやのプログラムです!」と、皆様に向けて胸を張って紹介できますし、皆様に支えていただけたので最後まで仕上げることができたな、と感じています。皆様にも是非ともご参考いただければ幸いです。

 

もともと、ブログタイトルの通り「忘備録」として作った本ブログの記事が色々な人のお役に立てていて、私自身にとっても支えになっていて、非常に嬉しく思います。

これからも、マイペースな更新になると思いますが、「中身のある忘備録」として続けていけたらと考えますので、引き続きどうぞ、本ブログをよろしくお願いいたします。

そして、「参考になったよ」という方は、是非ともコメントをいただければ幸いです。

 

重ねて第20回の記事公開を迎えられたことを記念し、これを支えていただいた皆様への感謝の辞にて結ばせていただきます。

 

2021年10月10日 ますたー c⌒っ.ω.)っ

 

前回記事:【Arduino自動化19】ねがいのかたまりで自動レイドバトル周回【完全放置で「けいけんアメ」「ヨロイこうせき」】

前々回記事:【Arduino自動化18】ローカルレイド自動配布【デリバードレイドでマックスこうせき】

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

関連記事:【Arduino自動化 番外編3】筆者の使用機材一覧【紹介記事】

他のArduino自動化:ポケモン剣盾Arduino自動化 カテゴリーの記事一覧

YouTubeチャンネル:ますたーの忘備録 - YouTube 【NEW!!】

---

*1:レイド配信を自動化しても参加側の手動操作が必要、ソロ周回を自動化しても目的のレイドとは限らない

*2:シリアル通信:有線で1本ないし2本の接続を行い、1bitごとにデータを送受信する方法

*3:いわゆる「ジャンパーコード」。ピンソケットが付いているArduinoならオス端子、ピンヘッダが付いている場合はメス端子が必要

*4:ご自身のボードのpin形状に応じたものを購入します。例えば、ピンヘッダが付いているボードを使っている方は「メス」のジャンパーワイヤーが必要になります。ただし、本ブログと同じものを使っているのであれば「オス」で大丈夫なので、間違えないようにしましょう

*5:もし、充電アダプタをつけていることにより、プログラムが先に動いているような挙動をする場合は、充電アダプタを外した状態で自動化を始め、途中で充電アダプタを差し込みましょう。ただし、その際は入力が2~3キーほどスキップされるような振る舞いをするため、必要に応じて別途手動で調整してください