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

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

今回は、番外編その3として、Arduinoでの自動化で私が「実際」に使っている機器類を紹介します。 

 

なお、機器全般・ソフトウェアの構成・セットアップは【Arduino自動化01】Arduino開発環境の導入をご覧ください。

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

 

概要

今回の記事では、実際に筆者が使っている機材を紹介します。参考になるよう、私が実際に購入した際の、購入日・購入金額・Amazonのリンクも載せます。

金額は私が購入した当時のものなので、あくまでも参考金額として参照ください(最新の金額はAmazonのリンク先で記載のものでご確認をお願いします)
※有線コントローラーのみAmazonではなく実店舗で購入しています。同型番のものを紹介します。

 

目次です。

私の使っている機材の一覧は最後に表で纏めています。

「説明はいらん」というかたは読み飛ばしてください⇒読み飛ばす

 

自動化の基本機材構成

まず、導入記事の一部繰り返しになりますが、Nintendo Switchの操作を自動化するにあたり、基本的な機材構成としては下記のものを準備する必要があります。

さらに、給電しながら自動化をする場合には、追加で

  • Switchドック(Switchの場合)または USBスタンド(Switch Liteの場合)
  • 充電アダプタ

が必要です。筆者としては、長時間放置できるからこそ自動化の意義があると考えるので、上記の下2つも合わせて使うことをおすすめします。

f:id:tangential_star:20201107181011p:plain
f:id:tangential_star:20201107181445p:plain
機材構成イメージ(左:おすすめ/右:最低限の構成)

 

必須ではないが、あると便利な機材

導入記事では紹介しておりませんでしたが、Nintendo Switchの操作を自動化するにあたって、前述の機材構成に加えて、あると便利だったものは下記の通りです。

なお、これらはいずれも筆者が実際に使っているものです

ただ、これらはあれば重宝しますが、「必須ではない」ので、無理に揃える必要はありません。まずは上記基本構成で自動化ができるようになってから必要に応じて購入すればOKです。

  • Arduino Leonardoのケース(カバー)
  • Switchドック用USBハブスタンド
  • USBスイッチ付きコネクタ
  • 有線コントローラー

以上に述べたこれらの機材について、次項からは1つずつ紹介します。 

 

 

実際に使用している機材の紹介

【前提】Arduino Leonardo(マイコン

まずは、ポケモン自動化の要となるマイコンArduino Leonardo」の紹介です。

自動化方法の調査記事でも紹介している通り、Arduinoには「Arduino UNO」系列と「Arduino Leonardo」系列の2種類があります。

見た目は似ていますが、これらの間でポケモン自動化の方法は大きく異なるので、UNOとLeonardoを間違えないように購入しましょう。

本ブログでは、導入記事でも紹介している通り、後者(Leonardo)を利用しています。

このArduino Leonardoには様々な互換機や類似品が売っています。もし、しっかりとした理解が無い状態で選ぶのであれば、筆者としては互換機ではなく純正の「Leonardo」を購入することを強くおすすめします。

ちなみに購入して1年経ちますが、全く壊れる気配がありません。

(筆者購入日:2020/1/28;購入時の金額:3,190円)

 

 

【前提】通信対応USBケーブル(USB-Aオス・USB-microBオス)

これは、すでにお持ちの方も多いと思います。

具体的には、少し前のAndroidスマホのPC通信ケーブルがまさにUSB-A・USB-microBですので、これが使えるかもしれません。

なお、SwitchまたはSwitch Liteと直接つなぐ(給電しない)場合は、USB-microB・USB-Cの通信対応USBケーブルを購入するか、別途USB-Aメス・USB-Cオスの変換口を購入してください。

念の為、USB-microB側をArduino Leonardoに挿し込み、USB-A側をSwitchのドックまたはUSBスタンドに挿し込みます。このケーブルはPCからArduinoにプログラムを書き込む際にも使うので、Arduino側(USB-microB側)は基本的に挿しっぱなしの運用になると思います。ケーブルが長すぎると邪魔になるので、自身のちょうどよい長さのものを買い揃えましょう。

ちなみに、よくあるミスのひとつとして「充電専用ケーブル」は使えないので、「通信対応」などと書かれているものを購入しましょう

(筆者購入日:2020/2/5;購入時の金額:598円)

 

 

【あると便利】Arduino用ケース(エンクロージャー)

Arduinoはその性質上、基盤がむき出しになることが多いです。そのため、不意に素手でベタベタ触っているときに誤って汚損・破損することもあります。しかし、Arduino Leonardoにはその専用のケースがありません

そこで、Arduino Leonardoに「UNO用」エンクロージャーをあてがうと、なんとなく良い感じに仕上がります(紹介していてアレですが、この使い方は公式に推奨されるものではありません!)。

本来は、Arduinoの基盤に空いた穴と付属のネジの位置が合致するためエンクロージャー内部で基盤が固定されるのですが、UNOとLeonardoでは基盤の配置や穴の位置が若干異なるため、これができません。

なので、私は、側面のアクリルを使わずに基盤を直接挟んでネジ固定する荒業で使っています。買って約1年経ちますが、壊れる気配が無いので安心しています。

(筆者購入日:2020/2/5;購入時の金額:340円)

f:id:tangential_star:20210128100318j:plain
f:id:tangential_star:20210128100139j:plain

筆者の使用しているマイコンArduino Leonardo」の外観
「UNO用」エンクロージャーでLeonardoを挟む荒業だが約1年間、問題なく使えている

 

【Liteの場合は必須】USBハブスタンド

Switch Liteを利用している方のうち、充電しながら自動化する場合には必須です(もちろん、すでに代替ドックなどのUSBポートと給電が同時にできるものを持っている方はそちらでもOKですし、そもそも給電しないのであれば不要です)。

純正ドックが使えるSwitch(Liteではない)では原則不要です。ただし、長時間使うことが前提になるので、「ドックに挿すと本体に熱がこもるのでは?」「ディスプレイが画面焼けするのでは?」などが気になる人は使ってみても良いかもしれません(テーブルモードになります)。私は、Switch Liteで自動化をするために買っています。ポータブルUSBハブスタンドの良いところは、なんと言ってもSwitch・Switch Liteいずれも安定してテーブルモードで利用できること。

筆者はこれの2ポート版も持っていますが、やっぱり横に広くなる4ポート版のほうが卓上で安定するのでなんとなく安心しています。

f:id:tangential_star:20210128004650j:plain
f:id:tangential_star:20210128004639j:plain
Nintendoライセンス商品の、ホリの「ポータブルUSBハブスタンド(4ポート)」。
Arduinoと充電アダプタを同時に挿せるので、給電しながら自動化ができる

なお、重要な欠点として、純正でありながら「Switch保護ケース」をつけたSwitchやSwitch Liteとは充電部分が干渉してしまい、使えないという点があります。

もし、普段は携帯モードで遊ぶ機会が多く、テーブルモードで遊ばないという人は注意が必要です。

(筆者購入日:2020/11/6;購入時の金額:3,550円)

 

【あると便利】Switchドック用USBハブスタンド

有機ELモデルのSwitchドックには使えません。

前述の通り、Switch(Liteではない)を利用する場合、純正のドックをUSBポートとして利用できます。

しかし、ドックの置き方にもよりますが、USBポートはドックの側面に付いているため、Switch本体を挿すとその裏側になり、Arduinoや有線コントローラーを挿しにくいという欠点があります。

また、別途冷却用ファンを使っている人などは、そもそもUSBポートが不足している、ということもあるかもしれません。

そこで、側面のUSBポートを1つ使い、Switchドックの「正面」にUSBポートを4つ拡張できるドック用のUSBハブスタンドを使うと便利です。特に、Arduinoを抜き差しして3日後の巣穴厳選をひたすら繰り返す【Arduino自動化05】乱数調整レイドの自動化では、横にあるよりも正面にある方が便利でした。

(筆者購入日:2020/11/14;購入時の金額:2,600円)

f:id:tangential_star:20201123010702g:plain

USBポートが正面にあるため抜き差しが楽々できるように

 

【あると便利】USBスイッチ付きコネクタ

スイッチひとつでON・OFFが切り替えられるコネクタです。

Arduino自動化で一番「買ってよかった」と感じている「個人的MVP機材」です。前述のドック用USBハブスタンドで、正面にUSBポートができたので、Arduinoの抜き差しも幾分手軽にできるようになったのですが、いかんせん、その抜き差しがめんどくさくなったのです(省力志向)。

ArduinoでSwitch操作を自動化する場合、他のコントローラーを予め抜いておく(無線コントローラーの場合は切断しておく)必要があります。

すなわち、「何かしらの自動化をするたびにコントローラーを抜いてArduinoを挿す」「自動化が終わったらArduinoを抜いてコントローラーを挿す」作業が必要になります。この抜き差しが大変に面倒くさいのです。

そこで、筆者はこれをなんと4個も買い(アホです)、上述のSwitchドック用USBハブスタンドにすべて挿し込み、ON・OFF切り替え対応機能をドックにつけました。

f:id:tangential_star:20210128150338j:plain
f:id:tangential_star:20210128150349j:plain
Switchドック用USBハブスタンドに、スイッチ付きUSBコネクタを4本挿し

結論、Arduinoもコントローラーも両方ともSwitchに挿しっぱなしで、スイッチでON・OFFが切り替えられるので、「今はコントローラーで操作したい!」というタイミングだけ有線コントローラー側をONにする、みたいなことができるように。

ちょっと操作が終わったらON・OFFを逆にするだけ、という極限の省力化に成功しました。しかも、Arduinoを普段ずっと挿しっぱなしにしても誤動作しなくなったので、Arduinoの収納場所を考えなくて済むようになりました(ただのズボラ)。

(筆者購入日:2020/12/5;購入時の金額:485円)

f:id:tangential_star:20210128161917j:plain

有線コントローラーとArduinoの2本刺しが可能。抜き差しせずに入力を切り替えられる

 

【余談】Switch本体のJoy-conカバーと有線コントローラー

最後に余談がてら、筆者の使っているNintendoSwitch周辺機器の紹介です。

もちろん、Switch本体の種類(Switch・Switch Lite)は自動化部分(ソフトウェア部分)とは本来、直接の関係はありません。前述の通り、私はSwitchとSwitch Liteの2台持ちですが、どちらも同じプログラムで動作しています。

f:id:tangential_star:20210127233334j:plain
f:id:tangential_star:20210127233348j:plain
筆者が愛用するSwitch本体。
ピカブイカラーのJoy-conと、Amazonで買ったシリコンカバーがお気に入りだ

筆者はピカブイセットでNintendo Switchを購入したので、Joy-conがピカブイカラーにちなんだ茶色と黄色になっています。

そこで、普段からさらにテンションを上げるために、別途購入したシリコンカバーをつけています。デザインがとてつもなくかわいいので気に入っています。

ただ、スティック部のシリコンカバーがずれやすかったり、単純に切り抜いただけのシリコンなのでボタンが埋もれて押しにくくなったりと、実用には多少ネックとなる部分もあります。

普段から別途コントローラーを使ったり、Arduinoで操作を自動化したりする場合にはこのデメリットは気にならないので、普段Switch用ドックに挿しっぱなし、という人は買ってみても良いかも?(私は欲しい物は多少値が張っても買っちゃうタイプなのでいらない人はやめましょう)

(筆者購入日:2020/12/16;購入時の金額:1,399円)

 

 

また、有線コントローラーも、もちろん、ピカブイの物にそろえて購入しています。淡い色合いにピカチュウイーブイが単色で入っているシンプルなデザインですが、とにかくかわいいです。スティックやボタンが茶色いのもアクセントになっていてすごく好みです。

やっぱり、普段遣いのものこそ、自分の好きなものを使ってテンションを上げていきたいですもんね。

 (筆者購入日:2020/7/13;購入時の金額:2,728円)

 

 

 

まとめ(購入リンク集)

本稿では、実際に私が使っている機材について紹介しました。

一覧で表示したものも準備しました。下記表を御覧ください。

 

筆者の使用機材一覧

必須 商品名 画像 Amazonリンク

備考(筆者コメント)

必須 Arduino Leonardo

https://amzn.to/3pvglNJ

筆者の使用マイコン。これがないと始まらない。

必須 通信対応USBケーブル(A-microB)

https://amzn.to/3aaSJrk

探せば家に5本くらいありそう。
  Arduino用ケース

https://amzn.to/36l50IC

本当はUNO用だが、Leonardoでも騙し騙し使っている。
  USBハブスタンド

https://amzn.to/2MfLFBD

Switch Liteでは必要。
  Switchドック用USBハブスタンド

https://amzn.to/39oyxTy

純正ドックのUSBポートをより使いやすくできる。おすすめ。有機ELモデルのドックには非対応
  USBスイッチ付きコネクタ

https://amzn.to/2NFc0tf

USBポートにON・OFFスイッチが付く。マジでおすすめ。
  Joy-conカバー

https://amzn.to/3ckmbO3

自動化には全く不要。だが、かわいい。

  有線コントローラー

https://amzn.to/2YnN6R6

あると便利だが、これでなくても良い。かわいい。

 

あとがき

思えば、今まで記事で使用機材を紹介する機会がなかったので、自動化記事も13件と増えてきましたので、良い機会かと思ってまとめてみました。

実は、私がArduino Leonardoを購入したのが2020年1月28日です。今日が2021年1月28日ですので、Arduino Leonardoを購入してから今日でちょうど1年なんですよね

1年前の今頃は、こうやってブログを作ろうと決意するなんて考えてもなかったですし、こんなにコロナが流行るなんて夢にも思ってなかったですから、世の中ってわからないなぁ、と感じます。

 

話が逸れましたが、少なくともArduino Leonardoが1年間壊れずに動いてくれたことに感謝しつつ、これからも使い倒していきます(笑)

 

皆様も、上記機材の一覧が役に立てば何よりですし、自分に合う機材構成で自動化ライフを楽しんでいただければ幸いです。

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

 

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

番外編1: 【Arduino自動化 番外編】マイコン導入にあたって気をつけるべきと思ったこと【ポケモン自動化 方法調査】

番外編2: 【Arduino自動化 番外編2】NintendoSwitchControllライブラリの導入と、自動化できる作業の一覧【ポケモン剣盾自動化】

 

【Arduino自動化13】全自動ポケモン逃がし

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

今回は、ボックスのポケモンを全自動で逃がし続ける自動化記事です。前回の全自動タマゴ受け取り&孵化とは対称的ですね。

※本記事ではポケモンを「逃がします」。利用に際してはくれぐれも自己責任でお願いします

 

Arduino自動化記事も本稿で第13回。これからもよろしくお願いします。

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

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

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

 

概要

本記事では、ボックスのポケモンを自動で逃がすプログラムを紹介します。孵化厳選などで溢れたボックスを整理できます。

f:id:tangential_star:20210121234428g:plain
f:id:tangential_star:20210121233732p:plain
完全放置でボックスのポケモンを逃がせる。1ボックスあたり約2分半だ

 

目次です。プログラムまで読み飛ばす方は⇒コチラからどうぞ

 

 

「いちらん」を使って歯抜け状態のポケモンボックスを左詰めに

効率的に逃がすためにも、まずは余談でボックス整理方法から。

タマゴ孵化で色厳選をすると、どうしてもボックスが孵化したポケモンで溢れてしまいます。そんなとき、1匹1匹を手動で逃がすのはとても大変です。

そんな時こそ自動化プログラムの出番…と言いたいところですが、前提として、自動化プログラムは「ボックスが歯抜け状態だと使えない」という欠点があります。

孵化したポケモンの中で「高個体値」「理想個体」「色違い」のポケモンを残そうと別ボックスに移動させてしまうと、どうしてもボックスの中が歯抜け状態になります。つまり、いわゆる「自動ポケモン逃がし」プログラムが使えなくなってしまいます。そこで、手動でポケモンを整列させる必要がありますが、1匹ずつずらすのも大変です。

f:id:tangential_star:20210122001659p:plain
f:id:tangential_star:20210122001952g:plain
左写真のように「歯抜け」があると自動化できない。右写真のように整列させる必要がある

実は、ボックスの機能「いちらん」を使うことで、効率的にポケモンたちを左詰めにできます。具体的には、他のボックスから「はんい」モード(緑のカーソル状態)で「いちらん」を経由して移動させると、空いている箇所に左詰めに自動で挿入してくれます。もし、今まで1匹ずつずらしていた人には、目からウロコのテクニックです。

f:id:tangential_star:20210121235612g:plain

ボックスの「いちらん」機能を使えば、1匹ずつずらさなくてもポケモンを左詰めにできる

 

逃がすポケモンの数をプログラムに書き込み

自動化プログラムの使い方に話を移します。

まずは、逃がすポケモンの数をプログラムに書き込みます。具体的には、「NUM_RELEASE_POKEMON」で指定しますので、ここに数字を入力してください。

例えば、8ボックス埋まっている場合は「30*8」または「240」と入力すればOKです。もちろん、30の倍数以外にも対応できます。

// 何匹、逃がしますか?
#define NUM_RELEASE_POKEMON (30*8+21) 

f:id:tangential_star:20210122215552g:plain

この画像なら、ボックス10~17までの8ボックスと21匹なので「30*8+21」と書けばOK

なお、前述の通り、ボックスのポケモンは「歯抜け」が無い状態でかつ、左詰めに整列させておいてください。

これが終わったら、B連打でボックスを閉じ、メニューを閉じ、フィールド画面に戻ってください(メニュー画面でカーソル位置が「ポケモン」なら準備完了です)。 

f:id:tangential_star:20210122221406p:plain
f:id:tangential_star:20210122221839g:plain
Xボタンでポケモンにカーソルが合った状態なら、自動でボックスを開いて逃がしてくれる

 

ソースコード

ポケモンをボックスに左詰めで整列させておき、Xでポケモンにカーソルが合う状態で、Arduinoを挿すだけの簡単設計です。

/* 
 *  ボックスのポケモン全自動逃がし
 *  【ボックスは30匹左詰めで埋まっていること】
 *  Xボタンでメニュー開いた時に「ポケモン」にカーソルが合っていること
 *  
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/
#include <SwitchControlLibrary.h>

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

// 何匹、逃がしますか?
#define NUM_RELEASE_POKEMON (30*8+21) 

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

// 現在、何匹逃したか
int current_poke = 0;

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

  current_poke = 0;

  // Xでメニュー画面を開く
  PushKey("X", HOLDTIME, 700);  // メニューを開き
  // ポケモンを開く
  PushKey("A", HOLDTIME, 1500); // ポケモンを開く
  PushKey("R", HOLDTIME, 1800); // ボックスへ
  
}


void loop() {

  if(current_poke >= NUM_RELEASE_POKEMON) for(;;); // 逃がす上限になったらプログラム終了

  PushKey("A", HOLDTIME, 350);  // ○○をどうする?
  PushKey("up", HOLDTIME, 250); // 
  PushKey("up", HOLDTIME, 250); // ⇒にがす
  PushKey("A", HOLDTIME, 800);  // 本当に逃がしますか?
  PushKey("up", HOLDTIME, 350); // ⇒はい
  PushKey("A", HOLDTIME, 1100);  // ○○を 逃がしてあげた ばいばい ○○
  PushKey("A", HOLDTIME, 300);  // ▼

  current_poke++; // 逃がしたポケモンの数をカウント


  if( current_poke %6 == 0 ){
    for(int i=0;i<5;i++)PushKey("left", HOLDTIME, 250); // 左にカーソルを戻し
    PushKey("down", HOLDTIME, 350); // 次のポケモンへ
  }else{
    PushKey("right", HOLDTIME, 350); // 次のポケモンへ
  }
  if( current_poke %30 == 0){
    PushKey("down", HOLDTIME, 350); // カーソルを左上に
    PushKey("down", HOLDTIME, 350); // もどして、
    PushKey("R", HOLDTIME, 800); // 30匹逃がし終わったら次のボックスへ
  }

}

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

 

あとがき

前回(第12回)全自動タマゴ受け取り&孵化の記事ではタマゴを受け取っては孵すを繰り返していましたが、今回の記事では打って変わって、ひたすらポケモンを逃がす記事でした。

私は孵化厳選した際に余ったポケモンたちを逃がすのが本当にめんどくさく、いつもポケモンバンクポケモンHomeに預けるのを繰り返していました。ところが、これらにも容量が上限あることに最近気づいてしまい、止む無く「逃がす」ことの重要性に気づいたのです。また、前回実装したタマゴ自動孵化プログラムのおかげで、気分が乗らなくてもタマゴ孵化が捗ってしまい、気づいたらボックスが埋まってしまうということも多くなったため、現在は重宝しています。

 

こうやって自分の手で「作って」「使って」みると、「世の中にすでにある自動化」はニーズがあるんだなぁ、と真の意味で納得できるということに気づかされます。本ブログが、皆様の需要にも応えられるように、世の中のものもしっかりと調べて行こうかなと思った次第です。

個人的には、第9回のポケモンキャンプでカレー自動調理は非常に満足の行く出来だったのですが、本当にニーズがないのか本記事にはなかなかアクセスがありません(ちなみに、「ポケモン カレー 自動化」で調べると、まだまだ駆け出しの本ブログでは珍しく、検索上位にランクインします。よかったら見てね←)

 

話が逸れましたが、本記事ならびに本ブログが皆様のポケモン自動化ライフの一助となれば幸いです。引き続き応援のほど、よろしくお願いいたします。

 

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

 

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

前回記事:【Arduino自動化12】全自動タマゴ受け取り&孵化

次の記事:【Arduino自動化14】ロトミIDくじ無限抽選

 

【Arduino自動化12】全自動タマゴ受け取り&孵化

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

今回は、預かり屋からタマゴを自動で受け取り、孵化までを自動で行う記事です。ついに全自動記事も第12回。引き続きよろしくお願いします。

 

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

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

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

 

概要

本記事では、ワイルドエリアの「預かり屋」でタマゴの自動受け取り&自動孵化を紹介します。完全放置で孵化厳選や色厳選作業をできるようにしてみました。

f:id:tangential_star:20210116214618g:plain
f:id:tangential_star:20210116214601p:plain
完全自動でタマゴの受け取りと孵化をひたすら行ってくれる

では、目次です。

ソースコードだけほしい方は読み飛ばしてください⇒読み飛ばす

 

本ブログの紹介環境で自動孵化を作りたかった

始めに、本ブログで「自動孵化」を取り扱おうと思ったきっかけについてです。

今まで、本ブログもまだまだ駆け出しということもあり、世間で公開されているArduino自動化には「無いもの」「新しいもの」をとにかく作ってきました(個人的には、第9回の全自動カレーづくりは大変な力作でした)。そして、本ブログも公開してから3ヶ月が過ぎ、自動化に関わる記事数も10を超え、月間1000アクセスも超えるようになりました。

本ブログでポケモン剣盾のArduino自動化にチャレンジを始めた人もいらっしゃるようなので、せっかくなら「同じ導入環境」で「使えるもの」を増やしたほうが親切かと思い、本ブログでも比較的メジャーな「孵化作業の自動化」に踏み切った次第です。

すなわち、本ブログの「導入記事」での環境で動いていることが(少なくとも筆者は)確認済のプログラムになります。そういった意味では需要があるのかと思います。

 

ほのおのからだ」を持つポケモンで孵化時間は半減

まず、孵化厳選で重要なことですが、必ず「ほのおのからだ」「じょうききかん」「マグマのよろい」のいずれかの特性を持つポケモンをパーティーに加えて孵化作業をしましょう。これらの特性をもつポケモンは、手持ちにいるだけで孵化に必要な歩数が半分になります。10匹以上孵化するなら、今から捕まえに行ってもお釣りが来るほど時間効率が変わります。持っていない人は今すぐ捕まえてください。

f:id:tangential_star:20210117010932g:plain
f:id:tangential_star:20210117011001g:plain

同じタマゴ・条件で「ほのおのからだ」を手持ちに加えた場合、孵化時間が半減する
※かかった時間はそれぞれ53秒、1分35秒だった(注:計測値/タマゴ:ニャビー

f:id:tangential_star:20210117011309p:plain
f:id:tangential_star:20210117011314p:plain
上記検証に使った手持ちポケモン。「ほのおのからだ」は高速孵化に効果てきめんだ

孵化の歩数を半減できる特性3つのうちいずれかを持っており、かつ、剣盾内(DLCなし)で捕まえやすいポケモンと言えば、タンドン系(タンドン・トロッゴン・セキタンザン)でしょうか。一応、他にもエンジンシティのジムミッションで出会える「ヒトモシ」もいますが、「もらいび」と確率半々なので、DLC無しで確実に捕まえるにはタンドン系が最安定と思います。なお、同特性を持つ剣盾内で出現するポケモンは、ポケモン徹底攻略で検索できますので合わせて参考にしてください。

ところで、剣盾にはDLC含めて「マグマのよろい」を持つポケモンマグマッグおよびマグカルゴ)は登場しませんでしたね。悲しい…。マグカルゴは私の好きなポケモンの1匹でした。当時、対戦はもちろん、ウルガモスファイアローが登場してからも孵化のお供に使っており、思い入れ深いポケモンでした。次作では帰ってきてほしいです。

 

受け取り作業と孵化作業の自動化

自動化に限らず、孵化作業において特筆して準備すべきものは、上記の通り「ほのおのからだ」持ちポケモンだけです。今回の自動化はタマゴを1匹ずつ「受け取り」「孵化」を繰り返すので、1回ごとの試行回数を増やすべく、手持ちポケモンは「ほのおのからだ」持ち1匹のみにしておいてください。

 

一応、効率的な孵化作業には「自転車」「連続した空きボックス」は必須かと思いますので、本ブログで紹介するプログラムもそれに準じます。なお、本ブログで扱う自動孵化は「ワイルドエリア」の預かり屋を活用します。理由は自転車でくるくると一周できるからです。

f:id:tangential_star:20210117014822p:plain
f:id:tangential_star:20210117014830p:plain
作業としての準備は「自転車」を準備し、「空きボックス」を連続させるだけだ

 

プログラムの使い方と修正箇所

本ブログのプログラムを使うにあたっては、下記冒頭箇所を必要に応じて修正してください。プログラム全文は後述します

// ↓タマゴ孵化にかかる時間[秒](実測でお願いします)
#define HATCH_TIME (57) // 何秒でタマゴが割れるかを計測して入れてください
#define ADV_OFFSET (3) // 育て屋前から何秒間はずれたところで自転車くるくるするか

// 何試行するか?
#define MAX_HATCH_POKEMON (30*9) 

// Xでメニュー開いたときのボタン配置
#define TOWNMAP_ROW (1) // 「タウンマップ」の行
#define TOWNMAP_COL (0) // 「タウンマップ」の列
#define POKEMON_ROW (0) // 「ポケモン」の行
#define POKEMON_COL (1) // 「ポケモン」の列

■1匹の孵化にかかる時間を入力する

計測は難しいですが、ポケモンの孵化歩数などを参考に入力しましょう。数字は「HATCH_TIME」に指定してください。現在デフォルトで入っている57はニャビー(孵化歩数:3840歩?に対して「ほのおのからだ」適用の数字)ですが、かなりざっくりアバウトな数字なので気にせず入力ください。その下の「ADV_OFFSET」は変更しなくて大丈夫です。

// ↓タマゴ孵化にかかる時間[秒](実測でお願いします)
#define HATCH_TIME (57) // 何秒でタマゴが割れるかを計測して入れてください
#define ADV_OFFSET (3) // 育て屋前から何秒間はずれたところで自転車くるくるするか

■空きボックスの数を指定

まず、前述の通り、空きボックス(30匹空白のボックス)を連続して配置してください。その個数を「MAX_HATCH_POKEMON」に適用します。具体的には、例えば、9ボックス連続で空っぽなら、30×9=「270」で指定すればOKです(あるいは計算式で「30*9」でもOK)。

// 何試行するか? 
#define MAX_HATCH_POKEMON (30*9)  

■Xボタンメニュー画面の位置を入力

Xで開くメニュー画面の「タウンマップ」「ポケモン」のボタンの位置情報を必要に応じて修正(プログラム全文は後述)してください。行(英語でrow)は、上の段が0、下の行が1という決まりで入れています。列(英語でcolumn)は、左端から順番に0, 1, 2, 3, 4になります。

具体的には、タウンマップの行列はそれぞれ「TOWNMAP_ROW」「TOWNMAP_COL」、ポケモンの行列は「POKEMON_ROW」「POKEMON_COL」で指定しているので、これらを修正ください。

 

// Xでメニュー開いたときのボタン配置 
#define TOWNMAP_ROW (1) // 「タウンマップ」の行
#define TOWNMAP_COL (0) // 「タウンマップ」の列
#define POKEMON_ROW (0) // 「ポケモン」の行
#define POKEMON_COL (1) // 「ポケモン」の列

よくわからない人は、プログラムではなくゲームのメニュー画面を、下記画像の順番で並び替えればOKです(この順番は、剣盾のシナリオを進めた際のそのままです)。

書き換え作業が終わったら、「タウンマップ」にカーソルをあわせてください。

f:id:tangential_star:20210117020954p:plain

「タウンマップ」「ポケモン」の位置(行列)をプログラムの指定箇所に書き込もう

 

ソースコード

使う前に、前述の通りXボタンで開くメニュー画面はプログラム上で編集しておき、Xボタンで開いたメニューのカーソルは「タウンマップ」に合う状態にしておいてください。

ワイルドエリアの「預かり屋」にポケモンを預け、手持ちは1匹・自転車に乗った状態で、かつ、「タマゴができている状態」でArduinoを挿すだけで、ひたすら「タマゴを受け取っては孵す」作業を繰り返してくれます。

/* 
 *  タマゴ自動受取@ワイルドエリア
 *  【手持ち1匹、ボックスは空き指定のこと】タウンマップにカーソルをあわせて
 *  自転車に乗り、タマゴが出来上がっている状態でワイルドエリア預かり屋手前で
 *  Arduinoを挿すだけで準備OK。
 *  
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h>

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


// ↓タマゴ孵化にかかる時間[秒](実測でお願いします)
#define HATCH_TIME (57) // 何秒でタマゴが割れるかを計測して入れてください
#define ADV_OFFSET (3) // 育て屋前から何秒間はずれたところで自転車くるくるするか

// 何試行するか?
#define MAX_HATCH_POKEMON (30*9) 

// Xでメニュー開いたときのボタン配置
#define TOWNMAP_ROW (1) // 「タウンマップ」の行
#define TOWNMAP_COL (0) // 「タウンマップ」の列
#define POKEMON_ROW (0) // 「ポケモン」の行
#define POKEMON_COL (1) // 「ポケモン」の列


// ★下表を参考に、↑の数字を書き換えてください★
// (Xでメニューを開いたときのボタンの位置のイメージ)
//  +     +     +     +     +  ←0段目(ROW=0)
//  +     +     +     +     +  ←1段目(ROW=1)
// 左橋から何個ずれるか(COLを0~4に)
// COL=0 COL=1 COL=2 COL=3 COL=4
//
// 例えば、メニューで「タウンマップ」が「下の段の1番目」にあるなら
// TOWNMAP_ROWを「1」に、TOWNMAP_COLを「0」に書き換えてください

void getEgg(void);
void hatchEgg(void);
int *SetCursor(int *cursor_position, int row, int col);
void SoratobuTaxi(int cursor_position[]);
void SendBox(int* cursor_position, int *hatched_num);

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 TiltRightStick(int direction_deg, double power, int holdtime, int delaytime);
void TiltStick(int leftdeg, double leftpower, int rightdeg, double rightpower, int holdtime, int delaytime);
void NextDayInCheatMode(void);



int current_cursor[2]; // Xボタンを開いたときのカーソルの位置
int hatched_num;       // タマゴからかえしたポケモンの数


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

  // Xボタンカーソルの位置を登録
  //(初期位置は、開いた時にタウンマップに合っている想定)
  current_cursor[ROW] = TOWNMAP_ROW;
  current_cursor[COL] = TOWNMAP_COL;
  // ★上記は環境に応じて書き換えてください★

  hatched_num = 0; // 何匹預けているか
  // もし、ボックスの続きからやる場合は、上記調整してください(非推奨)
}


void loop() {

  // 1匹タマゴ受け取り
  getEgg();
  // 1匹タマゴ孵化
  hatchEgg();
  // 孵化したポケモンの数を追加する
  hatched_num++;

  //上記5回繰り返すたびにボックス送り
  if(hatched_num>0&&hatched_num%5==0){ 
    SendBox(current_cursor, &hatched_num);
  }

  // 上限数に達したら処理終了
  if(hatched_num > MAX_HATCH_POKEMON)for(;;);
  delay(1000);

}

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;
}
void TiltRightStick(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().MoveRightStick(x,y);
  if(holdtime> 0){ // holdtime=0のときは押しっぱなし。
    delay(holdtime);
    SwitchControlLibrary().MoveRightStick(128,128); // 傾きを直す
  }
  if(delaytime>0) delay(delaytime);
  return;
}

void TiltStick(int leftdeg, double leftpower, int rightdeg, double rightpower, int holdtime, int delaytime){
  double rad;
  int x, y, rx, ry, lx, ly;

  rad = (double)leftdeg*PI/180.0; // 弧度法(ラジアン)変換
  x = (double)128*sin(rad)*leftpower;
  y = (double)-128*cos(rad)*leftpower;
  x += 128; y += 128;
  if(x >= 255) x=255; if(x <= 0) x=0;
  if(y >= 255) y=255; if(y <= 0) y=0;
  lx=x;ly=y;
  rad = (double)rightdeg*PI/180.0; // 弧度法(ラジアン)変換
  x = (double)128*sin(rad)*rightpower;
  y = (double)-128*cos(rad)*rightpower;
  x += 128; y += 128;
  if(x >= 255) x=255; if(x <= 0) x=0;
  if(y >= 255) y=255; if(y <= 0) y=0;
  rx=x;ry=y;

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


void NextDayInCheatMode(){
  // ★日付変更
  // Homeボタンを押して設定の画面へ移動
  PushKey("Home", HOLDTIME, 1000);

  // Home画面で「設定」を選ぶ
  PushKey("down", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 1500);

  // 設定画面で「日付と時刻」を開く
  PushKey("down", 1500, 105); // 1.5秒「↓」長押し
  PushKey("right", HOLDTIME, 105); 

  for(int i=0; i<4; i++){
    PushKey("down", HOLDTIME, 55);
  }
  PushKey("A", HOLDTIME, 500);

  // 時間設定(現在の日付と時刻を選ぶ)
  PushKey("down", HOLDTIME, 105);
  PushKey("down", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 500);

  // 時刻設定(日付の部分のみ回していく
  // 時間の変更
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("up",    HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("Home", HOLDTIME, 2000);
  PushKey("A", HOLDTIME, 1000);
  return;
}
int *SetCursor(int *cursor_position, int row, int col){

  if(cursor_position[COL] < col){
     for( ; cursor_position[COL] != col ; cursor_position[COL]++ )PushKey("right", HOLDTIME, 100);  
  }else if(cursor_position[COL] > col){
     for( ; cursor_position[COL] != col ; cursor_position[COL]-- )PushKey("left", HOLDTIME, 100);  
  }
  if(cursor_position[ROW] < row){
     for( ; cursor_position[ROW] != row ; cursor_position[ROW]++ )PushKey("down", HOLDTIME, 100);  
  }else if(cursor_position[ROW] > row){
     for( ; cursor_position[ROW] != row ; cursor_position[ROW]-- )PushKey("up", HOLDTIME, 100);  
  }

  return cursor_position;
}


void SoratobuTaxi(int* cursor_position){

  // タウンマップを開く
  PushKey("X", HOLDTIME, 700);  // メニューを開き

  // カーソルをタウンマップに揃える
  SetCursor(cursor_position, TOWNMAP_ROW,  TOWNMAP_COL);
  PushKey("A", HOLDTIME, 2700); // タウンマップを開く

  // TiltLeftStick( 45, 1.0, 50, 170); // カーソル合わせが必要な場所なら調整ください。

  // 空を飛ぶ(A)
  PushKey("A", HOLDTIME, 650);  // ○○まで そらとぶタクシーで 移動しますか?
  PushKey("A", HOLDTIME, 3800); // はい

  return;
}

void getEgg(void){
  SoratobuTaxi(current_cursor);
  TiltLeftStick( 185, 1.0, 50, 200); // ブリーダーおばさんの方向を向く
  PushKey("L", HOLDTIME, 190); // チラッと
  TiltLeftStick( 0, 1.0, 500, 240); // ブリーダーおばさんに向かう
  PushKey("A", HOLDTIME, 420);  // きみのポケモンがタマゴを持っていたんです!
  PushKey("B", HOLDTIME, 420);  // ほしいですよね?
  PushKey("A", HOLDTIME, 2800); // はい
  PushKey("B", HOLDTIME, 1300); // ○○は 預かり屋さん から タマゴを もらった!
  PushKey("B", HOLDTIME, 1100); // タマゴを 手持ちに 加えました!
  PushKey("B", HOLDTIME, 300); // 予備
  PushKey("B", HOLDTIME, 1600); // 大事に 育ててくださいね!
  PushKey("B", HOLDTIME, 300); // 予備

  return;
}


void hatchEgg(){
  // 時間を扱うための変数たち
  unsigned long int current_time=0;
  unsigned long int start_time=0;
  unsigned long int temp_time=0;
  
  // 向き修正(ブリーダーおばさんの反対側を向く)
  TiltLeftStick( 195, 1.0, 50, 200); // ちょっとだけ右側を向くと野生ポケモンと戦闘になりにくい
  PushKey("L", HOLDTIME, 190); // チラッと
  // SoratobuTaxi(current_cursor); // ※タクシーでも可

  TiltStick(0, 1.0, 0, 0, ADV_OFFSET*1000, 100); // 前にOFFSET秒だけ進んで(3秒ほど推奨)
  TiltStick(0, 1.0, 90, 1.0, 0, 0); // スティック傾けて
  for( start_time=millis(), current_time=start_time ; current_time - start_time < (HATCH_TIME-ADV_OFFSET)*1000UL ; current_time=millis() );
  TiltStick(0, 0, 0, 0, 0, 0); // 時間が経ったらスティックを初期位置に戻す

  // ・・・おや? ▼

  PushKey("A", HOLDTIME, 18500); // タマゴが かえって ○○が うまれた!
  PushKey("B", HOLDTIME, 4200); 

  return;
}

void SendBox(int* cursor_position, int *hatched_num){

  // ポケモンを開く
  PushKey("X", HOLDTIME, 700);  // メニューを開き
  // カーソルをポケモンに揃える
  SetCursor(cursor_position, POKEMON_ROW,  POKEMON_COL);
  PushKey("A", HOLDTIME, 1500); // ポケモンを開く
  PushKey("R", HOLDTIME, 1800); // ボックスへ
  
  if(*hatched_num > 30 && *hatched_num %30 == 5){
    PushKey("R", HOLDTIME, 800); // 30匹埋まっていれば次のボックスへ
  }

  PushKey("left", HOLDTIME, 150); // 手持ちにカーソルをあわせて
  PushKey("down", HOLDTIME, 150); // 手持ち2匹目にカーソル
  PushKey("Y", HOLDTIME, 150);
  PushKey("Y", HOLDTIME, 150); // 範囲指定モードに
  PushKey("A", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("A", HOLDTIME, 150); // ホールド

  PushKey("right", HOLDTIME, 150); 
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("A", HOLDTIME, 800); // いちらん をクリック
  PushKey("A", HOLDTIME, 400); // 今いるボックスに左詰めで5匹を入れる。

  PushKey("B", HOLDTIME, 600);  // いちらんを閉じ、ボックスに戻る
  PushKey("B", HOLDTIME, 2000); // ボックスを閉じ、手持ち画面に戻る
  PushKey("B", HOLDTIME, 2000); // 手持ち画面を閉じ、メニューに戻る
  PushKey("B", HOLDTIME, 2000); // メニュー画面を閉じ、フィールドに戻る
  
  return;
}

f:id:tangential_star:20210117025454g:plain

30匹でボックスが埋まっても右のボックスに自動に送るように実装しているので安心だ


あとがき

今回は、タマゴの自動受取と自動孵化を実装しました。これでよりこのブログも、偉大なる先駆者たちに近づけたかなぁと思います。何よりも自分の手でちゃんと実装ができてよかったです。これからも皆様、応援のほどよろしくお願いいたします

 

 

ところで、今回は、予想外のところでプログラミングにつまずきました。

あまり本ブログではプログラムの実装に関する話をする気は無いのですが、今後のプログラミングでも気をつけなきゃなと感じたのでここに「あとがき」として載せます。

Arduinoのint型は2バイトなので32767までしか扱えない

C言語で組み込みやマイコン以外しか触ってこなかった人にとってはかなり衝撃的というか、違和感があるのですが、なんと、int型なのに2バイト(16bit)しかありません。つまり、扱える整数は216=65536通りしかないため、unsigned(マイナスを考えないモード)でも65535までしか扱えないことになります。無論、プログラム中のリテラル(定数)も同様なので、演算の結果intの範囲を超えるとオーバーフローします。

具体的に何に困ったかというと、要するにそのままだと、Arduinoでは「32767ミリ秒以上」待機させることができない(⇒32秒までしか待機できない)ということでした。

…というか10本以上記事を書くだけプログラムしておきながら、今まで一切オーバーフローの可能性に気がつかなかった(=その桁数を扱わずに済んでいた)というのは、奇跡と言わざるを得ません。 一応、ケーススタディとして下記に具体例を載せて解説します。

うまく行かないプログラムソース例:「57秒待機させようとしたらオーバーフローする」

#define WAITTIME (57) // 57秒待機を予定

loop(){
  // 時間を扱うための変数たち
  unsigned long int current_time=0, start_time=0;

  // 57秒待機
  for( start_time=millis(), current_time=start_time ; current_time - start_time < WAITTIME*1000 ; current_time=millis() );
  // ↑このソースコードだとうまく行かずに無限ループする
}

このソースでは、「WAITTIME*1000」の計算結果が「57000」になりますが、これが「int型」で演算され、最大値32768以上になることで意図した結果とは異なる動きをしてしまいます。

結論:long intにキャストすればArduinoでも4バイトになる

前述のオーバーフローについて、原因は「文字定数もint型で演算されていること」です。結論、「long型(4バイト)」にキャストしておけば期待した通りに動作します。すなわち、リテラルに接尾辞「L」を付け加えることで解決できます。

改善したプログラムソース例(うまく動く例):「57秒待機するにはlong型にキャストしておく」

#define WAITTIME (57) // 57秒待機を予定

loop(){
  // 時間を扱うための変数たち
  unsigned long int current_time=0, start_time=0;

  // 57秒待機
  for( start_time=millis(), current_time=start_time ; current_time - start_time < WAITTIME*1000UL ; current_time=millis() );
  // ↑このソースコードだとうまく行く(1000を1000ULに変えただけ)
}

上記例では、1000の後に「UL」をつけてunsigned long int型にキャストしています。

接尾辞「L」をつけることで「long型」として扱われます。ちなみに「U」は「unsigned」の意なので、負の数を扱わないことを示します。

 

 *

 

以上、Arduinoのハードウェア仕様に関する内容にも触れたあとがきでした。この知見も、見る人が見るとためになる内容だと思います。

 

いずれにしても、皆様のポケモン自動化ライフがよりよいものになることを願っております。

  

追記:ニャビーの色違い無事に出せました! (1/17 11:27追記)

本ブログを執筆したのが本日1/17の午前4:09。ブログ投稿からわずか6時間、色違いに出会えました。また、このプログラムを動かし続けて7時間以上、ループの破綻なく、無事に動いておりました。これは個人的に安心できました。

なお、今回はわずか189試行で出会えました。これは強運。

f:id:tangential_star:20210117112445g:plain
f:id:tangential_star:20210117112553p:plain
7時間、ループ破綻なくプログラムは動き続け、その最中、無事に色違いに出会えました

 

改めて、皆様も良い自動化ライフを。

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

 

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

前記事:【Arduino自動化11】5番道路で全自動羽集め【DLC不要】

次記事:【Arduino自動化13】全自動ポケモン逃がし

 

【Arduino自動化11】5番道路で全自動羽集め【DLC不要】

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

今回は、「5番道路」の橋の上でひたすら努力値振りに使える「ハネ」系アイテムの自動回収を行う記事です。

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

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

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

 

概要

本記事では、ランクマバグ状態を利用した、羽系アイテムの自動回収を実装します。
※便宜上「ランクマバグ」と表記しますが、当ブログではランクマッチを使用しません。通信切断・初手降参の推奨もしません。

NintendoSwitch Ver.11.0アップデート対応済です。
★2021/9/20追記:NintendoSwitch Ver.13.0アップデート対応済です。
★2022/10/23追記:NintendoSwitch Ver.15.0アップデート対応済です。

f:id:tangential_star:20210111155633p:plain
f:id:tangential_star:20210111121951g:plain
完全自動で5番道路の育て屋から羽を10枚回収する。1試行あたり約56秒だ


では、目次です。

ソースコードだけほしい方は読み飛ばしてください⇒読み飛ばす

 

羽集めで端数の努力値振りも簡単に

今回の記事で集める「たいりょくのハネ」「ちりょくのハネ」などのいわゆる「ハネ系アイテム」は、 ポケモンの基礎ポイント(努力値)を1増やすことができます。

ポケモンの育成をする上で努力値振りという作業をしますが、極振り(252振り)以外の端数で努力値を扱う場合には、10単位でしか努力値が増やせない各種ドーピングアイテム(タウリンブロムヘキシンなど)では対応できないため、1単位で努力値を増やせる「○○のハネ」は大変重宝します。

f:id:tangential_star:20210111131402p:plain
f:id:tangential_star:20210111131407p:plain
羽系アイテムなら両刀の配分や、素早さ抜き調整で例えばS28振りなど柔軟に対応できる

 

日付変更+マップ切り替えで無限に羽が集まる

「○○のハネ」はドロップアイテムとしてガラル地方の様々な場所に隠しアイテムとして落ちています。

これらの場所で拾える羽系アイテムは、1日経過で「同じ場所」に復活するため、これらの拾えるポイントを事前に覚えておけば、毎日これらのポイントを周回することでコツコツと集めることができます(例えば「5ばんどうろ」の橋の上や「エンジンシティはずれ」の橋の上、ヨロイじまの海上など)。

f:id:tangential_star:20210111122110p:plain
f:id:tangential_star:20210111155817p:plain
羽系アイテムは、橋の上や海の上など、様々な場所に隠しアイテムとして落ちている

条件についてより厳密には、日付の切り替え+マップ切り替え(そらとぶタクシー含む)の条件2つを満たすと復活するため、巣穴が近くにある場合には、いわゆる「日付バグ」との併用で無限に回収し続けることができます。

手動でやる場合は、ヨロイじまの「ワークアウトのうみ」の岩(一礼野原の駅を出て目の前に見える海上の岩)がおすすめです。岩場を1周するだけで羽が集められ、ポケモンの巣穴もあるため日付変更ペナルティの回避もできます。前述の通り羽の落ちている場所は常に一定なので、場所を覚えれば効率的に回収できます。

f:id:tangential_star:20210111151804g:plain

日付変更+そらとぶタクシーで同じ場所に羽系アイテムが復活する。無限に回収可能だ

 

ランクマバグで効率的な日付変更が可能に

前回の記事でも紹介しましたが、ランクマッチ1戦の直後や、YY通信のカジュアルバトルで機内モードに変更した際に、日付変更ペナルティが解除されるバグ(通称:ランクマバグ)があります。これを活用することで、短い時間で場所を選ばず効率的に日付変更ができるようになります。

なお、ランクマッチを使用しない方法での「ランクマバグ状態」移行は下記手順でできます。画像は過去記事の使いまわしです。あしからず。

  1. YY通信で「通信対戦」を行う(ローカル通信)
  2. もう一台のSwitch・剣盾を使ってその通信対戦に応じる
  3. 戦闘が始まったらHomeボタン長押し→「機内モード」をONに変更する
  4. エラーが発生するのでそれを閉じて、「にげる」選択
  5. フィールド画面に戻ってくる(ランクマバグ状態に移行完了)
f:id:tangential_star:20201122213022g:plain
f:id:tangential_star:20201122213037g:plain
YY通信でローカル対戦を募る。バトルが始まったらHomeボタン長押しで機内モード
f:id:tangential_star:20201122213148g:plain
f:id:tangential_star:20201122213205g:plain
エラーが出たら「にげる」で対戦終了。この状態で時刻変更すると一瞬画面が暗転する

 

羽集めの自動化は「5番道路」が最適

前述の通り、羽を集めるには、大きく「日付変更+マップ切り替え」と「羽を拾う動作」の2つを繰り返す必要があります。自動化する上で、この「日付変更+マップ切り替え」が結構な曲者です。

上記でおすすめした「ワークアウトのうみ」では、近くに「そらとぶタクシー」で移動できるポイントがあるため都合が良いのですが、海上に位置するため、道中、ポケモンに遭遇する可能性が高いです。具体的には、サメハダーに襲われたりタマンタが跳ねてきたり、他にもキャモメが空を飛んでいたりなどで不意な戦闘が発生するため、一定の動きでプログラミングしてしまうと、ループが途切れてしまいます。

そこで、羽集めの自動化には、「そらとぶタクシー」で近くにアクセスできて、ポケモンとの遭遇がほぼ発生しない「5番道路」を活用します。

「そらとぶタクシー」でこれらが選択できるよう、あらかじめ「5番道路」のキャンプ場と「預り屋さん」に足を運んでおきましょう。

f:id:tangential_star:20210111161229p:plain
f:id:tangential_star:20210111161238p:plain
「キャンプ場」「預り屋さん」を訪れておけば、そらとぶタクシーで移動できるようになる

ちなみに、5番道路を活用することで「DLCが不要」 という副次的なメリットもあります。剣盾ビギナーの方にはそういう意味でもおすすめと言えます。

 

自動化プログラムの使い方

今回のプログラム(ソースコードは後述の通り)は、上記の通り5番道路で羽集めを自動で行うものです。できるだけ効率化を図るため、下記条件で動くようにしました。

この状態でArduinoを挿すと、「日付変更⇒そらとぶタクシーで預り屋に移動⇒10枚の羽を拾う」をひたすらループしてくれます。

  1. ランクマバグ状態であること
  2. そらとぶタクシーで「5ばんどうろ」「あずかりやさん」が選択可能なこと
  3. 「話の速さ」が「はやい」になっていること
  4. Xボタンでメニューを開いた時に「タウンマップ」にカーソルが合うこと
  5. 5番道路で自転車に乗っていること
f:id:tangential_star:20210111162925p:plain
f:id:tangential_star:20210111162931p:plain
Xボタンでタウンマップがカーソルにあうこと・自転車に乗っていることを確認しよう

 

ソースコード

ランクマバグ状態・5番道路で自転車に乗ってArduinoを挿すだけの簡単仕様です。

2021/9/20追記:NintendoSwitch Ver.13.0アップデート対応済(詳細はこちら
2022/10/23追記:NintendoSwitch Ver.15.0アップデート対応済
※Switch Liteをご利用の方は「#define SWITCH_VER (15)」を「#define SWITCH_VER (12)」に書き換えてご利用ください。ただし、未検証です。

/* ★NintendoSwitchのファームウェアVer13に対応版(2021/9/20)★
 * ★NintendoSwitchのファームウェアVer15に対応版(2022/10/23)★ 
 *  
 *  羽集め@5番道路(育て屋)c⌒っ.ω.)っ 
 *  【ランクマバグ状態で使うこと!】
 *  Xボタンで「タウンマップ」にカーソルが合っていること、自転車に乗っていること
 *  
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h>

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

#define SWITCH_VER (15)
// 2021/9/20、SwitchのVer13アップデートに伴う追記
// Switchのバージョンを整数で入力(例:12.2.1⇒12, 13.0.0⇒13, 15.0.0→15)

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() {

  // ★ランクマバグ状態時に日付を回すやつ
  NextDayInCheatMode();

  // ★5番道路の預かり屋に空を飛ぶ。
  PushKey("X", HOLDTIME, 700);  // メニューを開き
  PushKey("A", HOLDTIME, 2700); // タウンマップを開く
  TiltLeftStick( 45, 1.0, 50, 170); // 5番道路から預かり屋にカーソル合わせ
  // 空を飛ぶ(A)
  PushKey("A", HOLDTIME, 650);  // 5番道路まで そらとぶタクシーで 移動しますか?
  PushKey("A", HOLDTIME, 3500); // はい


  // ★羽集め(育て屋前からスタート)
  // 1個め
  TiltLeftStick( 93, 1.0, 3000, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 2個め
  TiltLeftStick( 72, 1.0, 1950, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 3個め
  TiltLeftStick( 103, 1.0, 1460, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 4個め
  TiltLeftStick( 105, 1.0, 1250, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 5個め
  TiltLeftStick( 59, 1.0, 750, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);


  // 6個め
  TiltLeftStick( 100, 1.0, 2690, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);


  // 7個めnew
  TiltLeftStick( 63, 1.0, 1620, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 8個めnew
  TiltLeftStick( 120, 1.0, 1950, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 9個めnew
  TiltLeftStick( 69, 1.0, 1800, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 10個めnew
  TiltLeftStick( 125, 1.0, 900, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  delay(1000);

}

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

void NextDayInCheatMode(){
  // ★日付変更
  // Homeボタンを押して設定の画面へ移動
  PushKey("Home", HOLDTIME, 1000);

  // Home画面で「設定」を選ぶ
  PushKey("down", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 1500);

  // 設定画面で「日付と時刻」を開く
  PushKey("down", 1500, 105); // 1.5秒「↓」長押し
  PushKey("right", HOLDTIME, 105); 


#if (SWITCH_VER >= 15)
  // Switchのver15アプデでBluetoothオーディオの設定が変わった?ので、微修正。
  // ※本体のバッテリー残量(%)にずれるようになったので。
  PushKey("down", 750, 55);
#elif (SWITCH_VER >= 13)
  // Switchのアプデで「ドックの更新」項目が追加されたので
  // 日付変更をする場合には「↓」キー長押しが必要に。
  PushKey("down", 780, 55); 
#else
  // ver12まで(13アプデ前)は、1ページに収まるのでそのままでOK
  for(int i=0; i<4; i++){
    PushKey("down", HOLDTIME, 55);
  }
#endif
  
  PushKey("A", HOLDTIME, 500);

  // 時間設定(現在の日付と時刻を選ぶ)
  PushKey("down", HOLDTIME, 105);
  PushKey("down", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 500);

  // 時刻設定(日付の部分のみ回していく
  // 時間の変更
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("up",    HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("Home", HOLDTIME, 2000);
  PushKey("A", HOLDTIME, 1000);
  return;
}

日付変更処理部分を関数にしたので、loop()の中が多少読みやすくなりました。

 

あとがき

今回の記事では、対応する努力値を「1」だけ増やすことができる、いわゆる「羽系アイテム」の自動回収について紹介しました。

前回の「【Arduino自動化10】きのみ・ぼんぐり自動回収」を使えば、対応する努力値を「10」下げられるので、これらを併用することで、努力値調整がかなり簡便に行えるようになりました。

さらに、技レコードやミント、各種カプセルなどは、それぞれ「ワット(【Arduino自動化03】ワット稼ぎ・自動回収)」、「バトルポイント(【Arduino自動化08】バトルタワー自動周回【BP稼ぎ】)」や「マックスこうせき(【Arduino自動化06】完全放置「マックスこうせき」集め)」で引き換えられるため、今までのものも含めて育成環境の整備がしっかりとできるようになってきたかな、と感じています。

 

アイテムなどの回収作業をArduinoで自動化でき、さらに副次的に、育成論の検討や対戦に時間を割けるようになるのは、まさに一石二鳥なのではないかと思います。皆様のプレイスタイルに応じてうまくご活用いただければ幸いです。

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

 

前記事:【Arduino自動化10】きのみ・ぼんぐり自動回収

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

次の記事:【Arduino自動化12】全自動タマゴ受け取り&孵化

【Arduino自動化10】きのみ・ぼんぐり自動回収

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

今回は、「きのみがとれる木」を自動で揺らし、完全放置で「努力値下げきのみ」や「ぼんぐり」などを集めるArduino Leonardo自動化の記事です。
記念すべき10個目の「Arduino自動化」に関する記事です。引き続きよろしくお願いします!

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

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

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

概要

本記事では、ランクマバグ状態を利用した、きのみの自動回収を実装します。
※便宜上「ランクマバグ」と表記しますが、当ブログではランクマッチを使用しません。通信切断・初手降参の推奨もしません。

NintendoSwitch Ver.11.0アップデート対応済です。
★2021/9/20追記:NintendoSwitch Ver.13.0アップデート対応済です。
★2022/10/23追記:NintendoSwitch Ver.15.0アップデート対応済です。

f:id:tangential_star:20210105151958g:plain
f:id:tangential_star:20210105151934p:plain
木を揺らして日付を変えるまで約28秒。ぼんぐり・きのみの高速回収が可能だ


では、目次です。

 

つくったきっかけ

きのみ・ぼんぐりの自動回収については、すでに沢山のブログで紹介・掲載されていると思います。一方で、当ブログへの検索流入を調べたところ「ぼんぐり 自動化」で検索してアクセスされる方が一定数いらっしゃるため、おそらくまだまだ需要があるかと思いましたので、更新するに至った次第です。

また、他のブログでは、先月のNintendo SwitchのVer.11のアップデート(Nintendo OnlineボタンがHome画面に実装された奴)に対応していないものも一定数見受けられるため、そういう意味でも需要があるかな、と思います。

一応、ベーシックな「きのみ高速回収のやりかた」に沿って執筆していますので、Arduinoをお持ちでない方にも楽しく読めるように心がけています

なお、「きのみ回収の使い方も手順も知っとるわ!ソースコードだけくれ!」 という方は読み飛ばしてください⇒読み飛ばす

 

集めたいきのみ・ぼんぐりが出やすい木を揺らそう

きのみ・ぼんぐりを効率的に集めるにあたって、「どこの木を揺らせば良いのか?」が大変重要です。

例えば、「こうげき」の基礎ポイント(努力値)を「10」下げる効果を持つ「ネコブのみ」を集める場合、7番道路やナックル丘陵の木からランダムに入手できますが、ヨロイじまの「ワークアウトの海」の木(「並ぶ島の海」側)であれば「ネコブのみ」が確定で落ちてきます(それ以外のきのみは落ちてきません)。

すなわち、揺らして手に入るきのみの種類や量が場所によって違うため、狙ったきのみが出やすい木で揺らし続けることが重要です。

特に需要が大きいかと思いますので、各種努力値下げきのみについて、下表におすすめの木の場所を纏めました(結構頑張りました)。ご参考ください。

 

狙う「きのみ」が落ちてくる木の場所の一覧(アルファベット表記は「ポケモン徹底攻略」に準じる)

「きのみ」の名前 きのみの効果 おすすめの場所 備考
ぼんぐり全種 ガンテツボールの
材料になる
集中の森
(ヨロイじま)
当エリア内の
どの木でもOK。
※出やすさに偏りあり
努力値下げきのみ全種
※個別に集める場合は
それぞれ下記推奨
対応する努力値
10だけ下げる
巨人の鏡池
(ワイルドエリア)
DLC不要。
当エリアには1本しか
木が無いため迷わない
ザロクのみ 「HP」努力値
10だけ下げる

離れ島海域(ヨロイじま)

f:id:tangential_star:20210108171554g:plain
離れ島海域の木。
チャレンジビーチから岩の間を通り左奥へ進もう。
ネコブのみ 「こうげき」努力値
10だけ下げる

ボールレイクの湖畔(カンムリせつげん)

f:id:tangential_star:20210105180104g:plain
ボールレイクの湖畔「C」の木。
ダイ木の丘から池を左回りに進もう。
タポルのみ 「ぼうぎょ」努力値
10だけ下げる

ボールレイクの湖畔(カンムリせつげん)

f:id:tangential_star:20210105194230g:plain
ボールレイクの湖畔「F」の木。
ダイ木の丘から池を半周分、左へ進もう。
ロメのみ 「とくこう」努力値
10だけ下げる

ボールレイクの湖畔(カンムリせつげん)

f:id:tangential_star:20210105194352g:plain
ボールレイクの湖畔「H」の木。
ダイ木の丘から右に行き、下りたら左に進もう。
ウブのみ 「とくぼう」努力値
10だけ下げる

鍛錬平原(ヨロイじま)

f:id:tangential_star:20210108121838g:plain
鍛錬平原「巣穴G側」の木。
掘り出しおやじの逆方向へまっすぐ進もう。
マトマのみ 「すばやさ」努力値
10だけ下げる

ボールレイクの湖畔(カンムリせつげん)

f:id:tangential_star:20210105194555g:plain
ボールレイクの湖畔「A」の木。
ダイ木の丘から左に進み、登ったら右奥に進もう。

 

ランクマバグ状態で効率的にきのみを回収する

ランクマバグ状態ならきのみが即復活

きのみ・ぼんぐりの効率的な回収に関しては、ランクマッチ後やYY通信のカジュアルバトルで機内モードに変更した際に日付変更ペナルティが解除されるバグ(通称:ランクマバグ)を活用します。ランクマバグ状態であれば、きのみを回収してすぐにHome画面から日付を変更することで、再度きのみを復活させることができます

f:id:tangential_star:20210105165439g:plain

ランクマバグ状態なら、日付を進めるだけできのみが即復活する

Switch2台あればランクマッチ不要で「ランクマバグ」状態に

いわゆる「ランクマバグ状態」への移行は、ランクマッチ1戦の代わりに、下記手順でも可能です。Nintendo Online未加入など、ランクマッチができない場合に重宝します。

なお、Switchを2台用意できないなどでこの方法が使えない人は素直にランクマッチで1戦してください(その場合、Nintendo Onlineへの加入が必要です)。

  1. YY通信で「通信対戦」を行う(ローカル通信)
  2. もう一台のSwitch・剣盾を使ってその通信対戦に応じる
  3. 戦闘が始まったらHomeボタン長押し→「機内モード」をONに変更する
  4. エラーが発生するのでそれを閉じて、「にげる」選択
  5. フィールド画面に戻ってくる(ランクマバグ状態に移行完了)

ちなみに、しっかりとランクマバグ状態に移行できているかを確認するためには、一度Home画面に戻り、もう一度ゲーム画面に戻ると分かります。ゲーム画面に復帰した際に、一瞬だけ画面全体がチラッと暗転すればOKです。

なお、【Arduino自動化05】乱数調整レイドで色違いパッチルドン探し【冠の雪原】の記事で紹介した時とは違い、今回は一度に大量の日付変更を行わない前提なので、必ずしもポケモンセンターに移動する必要はありません(画像は使いまわしです)。

f:id:tangential_star:20201122213022g:plain
f:id:tangential_star:20201122213037g:plain
YY通信でローカル対戦を募る。バトルが始まったらHomeボタン長押しで機内モード
f:id:tangential_star:20201122213148g:plain
f:id:tangential_star:20201122213205g:plain
エラーが出たら「にげる」で対戦終了。この状態で時刻変更すると一瞬画面が暗転する

 

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

「ランクマバグ状態」で、揺らしたい木の目の前でArduinoを挿すだけの簡単仕様です。

2021/9/20追記:NintendoSwitch Ver.13.0アップデート対応済(詳細はこちら
2022/10/23追記:NintendoSwitch Ver.15.0アップデート対応済
※Switch Liteをご利用の方は「#define SWITCH_VER (15)」を「#define SWITCH_VER (12)」に書き換えてご利用ください。

/* ★NintendoSwitchのファームウェアVer13に対応版(2021/9/20)★
 * ★NintendoSwitchのファームウェアVer15に対応版(2022/10/23)★ 
 *   
 *  きのみ・ぼんぐり回収c⌒っ.ω.)っ 
 *  【ランクマバグ状態必須!!】
 *  
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h>

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

#define SWITCH_VER (15)
// 2021/9/20、SwitchのVer13アップデートに伴う追記
// Switchのバージョンを整数で入力(例:12.2.1⇒12, 13.0.0⇒13, 15.0.0→15)

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() {

  // ★きのみ回収
  PushKey("A", HOLDTIME, 600); // きのみが とれる 木だ! 揺らしてみますか?
  PushKey("A", HOLDTIME, 900); // ▼ or ……いまは きのみが ないみたい 
  PushKey("A", HOLDTIME, 3200);// はい
  PushKey("B", HOLDTIME, 600); // ○○が ○個 落ちてきた!▼
  PushKey("B", HOLDTIME, 600); // きのみが ○個 落ちている 「やめる」※以降はB連打
  PushKey("B", HOLDTIME, 600); // ○○は 木から落とした きのみを 拾いあげた! 
  PushKey("B", HOLDTIME, 600); // ○○を ○個 手に入れた!
  PushKey("B", HOLDTIME, 600);
  PushKey("B", HOLDTIME, 600);
  PushKey("B", HOLDTIME, 600);
  PushKey("B", HOLDTIME, 600); // 2種類までならここまででOK(ボールレイクの湖畔 C のネコブのみ・イバンのみ など)

  PushKey("B", HOLDTIME, 600);
  PushKey("B", HOLDTIME, 600);
  PushKey("B", HOLDTIME, 600);
  PushKey("B", HOLDTIME, 600);
  PushKey("B", HOLDTIME, 600); // 1試行で3種落ちる木の場合はここまで処理必要


  // ★日付変更

  // Homeボタンを押して設定の画面へ移動
  PushKey("Home", HOLDTIME, 1000);

  // Home画面で「設定」を選ぶ
  PushKey("down", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 1500);

  // 設定画面で「日付と時刻」を開く
  PushKey("down", 1500, 105); // 1.5秒「↓」長押し
  PushKey("right", HOLDTIME, 105); 

#if (SWITCH_VER >= 15)
  // Switchのver15アプデでBluetoothオーディオの設定が変わった?ので、微修正。
  // ※本体のバッテリー残量(%)にずれるようになったので。
  PushKey("down", 750, 105);
#elif (SWITCH_VER >= 13)
  // Switchのアプデで「ドックの更新」項目が追加されたので
  // 日付変更をする場合には「↓」キー長押しが必要に。
  PushKey("down", 780, 105);
#else
  // ver12まで(13アプデ前)は、1ページに収まるのでそのままでOK
  for(int i=0; i<4; i++){
    PushKey("down", (int)HOLDTIME/2, 105);
  }
#endif
  
  PushKey("A", HOLDTIME, 500);

  // 時間設定(現在の日付と時刻を選ぶ)
  PushKey("down", HOLDTIME, 105);
  PushKey("down", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 500);

  // 時刻設定(日付の部分のみ回していく
  // 時間の変更
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("up",    HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("Home", HOLDTIME, 2000);
  PushKey("A", HOLDTIME, 1000);
  
}

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

 

 結構お手軽ですね。

 

あとがき 

今回は、いわゆる「ランクマバグ」を利用した、全自動「きのみ」「ぼんぐり」高速回収のプログラムを紹介しました。

紹介していて言うのも変な話なのですが、個人的には「ランクマバグ」という表現があまり好きではありません。というのも、一昔前まで、ランクマッチでの初手降参や、見ず知らずの人とのYY通信から機内モード(=切断)を通して「ランクマバグ」に移行することを紹介するWebサイトや動画が散見され、各人のリテラシーが問われるようなバグの活用と周知が成されていたからです。

当時の煽りを目の当たりにしていた私からすると、ランクマバグ自体が敬遠されるべきものとしてイメージされていました(ゆえに、自動化記事2つ目「【Arduino自動化02】雪中渓谷のワットショップの目玉商品を全自動回収」についても、ランクマバグを利用しない方法で作成していました)。

今でこそ、マナーアップ啓蒙も含めた紹介動画やWeb攻略情報の掲載がされており、真摯にランクマッチ1戦で紹介されたり、各人でSwitchを2台用意した上での機内モード活用が紹介されたりしているため、一昔前ほどの心配は杞憂なのでしょうが、そういった背景心情があった上での記事執筆だとご理解いただければと思います。

つきましては、釈迦に説法かとは思いますが、読者の皆様においても、上述のような「他人に迷惑がかかる方法」でのバグ活用はお控えいただくよう、お願いします。

ところで、2020年11月7日 に本ブログを始めてから、明日で3ヶ月を迎えます。PV数・検索流入数も少しずつですが増えてきており、お陰様でモチベーションも保ちながら更新ができています。まだまだ駆け出しの当ブログですが、もし皆様のお役に立てるところがあれば幸いです。

もし、「いつも見てます!」とか「応援してます!」と言ったハートウォーミングな御仁がいらっしゃれば、「コメント」や「はてなスター」を残していただけますと幸いです。

 

どうぞ、引き続きよろしくお願いいたします。

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

 

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

前回記事:【Arduino自動化09】ポケモンキャンプでカレー自動調理【カレーのあかし】

次の記事:【Arduino自動化11】5番道路で全自動羽集め【DLC不要】

【Arduino自動化09】ポケモンキャンプでカレー自動調理【カレーのあかし】

ますたーです。あけましておめでとうございます。2021年もよろしくお願いいたします。

 

今回は、ポケモンキャンプで「カレー」を自動で調理するArduino Leonardo自動化の記事です。

なお、Arduino Leonardo自動化の導入と、機材構成については導入記事をご覧ください。

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

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

 

概要

本記事では、ポケモン剣盾の「ポケモンキャンプ」の「カレー」づくりを自動で行います。「クラボのみ」を6個ずつ消費(食材不要)して、マホミル級のカレー完全放置作ってくれます。カレーのあかしを持ったポケモンの厳選が効率的にできます。 

2021/6/9追記:ポケモン捕獲までの完全自動化に成功しました。合わせてご参考ください⇒【Arduino自動化09ex】「カレーのあかし」持ちポケモンの完全自動厳選【キャンプでカレー】

f:id:tangential_star:20210102002716g:plain
f:id:tangential_star:20210102102016p:plain
ポケモンキャンプのカレー作りを自動化。食材不要・クラボのみ6個でマホミル級になる

 

それでは、今回の目次です。

 

つくったきっかけ

Twitterのタイムラインを眺めていたところ「カレーのあかし」を持ったポケモンが手に入る場所についてまとめた投稿を見かけて、何故か猛烈に『カレーずきなモルペコ』が欲しい!」と思ってしまったので作りました。

 

調理後、まれに「カレーのあかし」を持ったポケモンが見つかる

最初にカレー作りの目的について紹介します。「目的なんかわかりきっている!使い方とソースコードだけ寄越せ!」という方は読み飛ばしてください⇒読み飛ばす

 

まず、ワイルドエリア・ヨロイじま・カンムリせつげん以外の道路上(例えば9番道路など)で、「ポケモンキャンプ」でカレーを作ると、時々、その香りに釣られてポケモンがやってきます。話しかけると仲間になってくれます。

f:id:tangential_star:20210102102901g:plain
f:id:tangential_star:20210102103030p:plain
カレー調理後、香りに釣られてポケモンが遊びに来る。話しかけると仲間になってくれる

さらに、カレー調理後にやってきたポケモン「カレーのあかし」リボンを必ず持っているという特徴があります。

いわゆる「証厳選」をしたことがある人はご存知かと思いますが、「○○のあかし」を持つポケモンは野生でのみ出現します。ところが、そのポケモンが証を持っているかどうかは捕まえないと分かりません。しかも、その所持率はおおよそ2%で、なんと証の種類もランダムです。狙った証をつけるのは至難の業と言えます。

一方で、カレーを作った後の野生のポケモンは必ず「カレーのあかし」を持っているため、狙った証を100%つけることができます。今回のお目当ては、「カレーのあかし」を持ったモルペコです(モルペコの特性は「はらぺこスイッチ」ですし、「カレーずき」の二つ名はまさにピッタリですよね)。

f:id:tangential_star:20210102011021p:plain
f:id:tangential_star:20210102011451g:plain
「カレーのあかし」を持ったポケモンは、登場時に「カレーずきな」という二つ名が付く

なお、野生ポケモンが来てくれる確率は、手持ちポケモンのなつき度と、作ったカレーのランクによって変わります。

有志による検証によれば、最もポケモンが出やすいカレーのランクは「マホミル級」で、その確率は1/7(約14%)だそうです。ちなみに、左記は手持ちがなつき度MAX6匹の状態でのお話です。なつき度はカレーを作ると上がるので、カレーずかん埋めも兼ねて、とりあえず無限にカレーを作り続けるのが良いでしょう(適当)。

f:id:tangential_star:20210102102016p:plain
f:id:tangential_star:20210102120251p:plain
「マホミル級」のカレーを作ると野生のポケモンが来やすい。確率は最大で1/7ほど

 

「マホミル級」のカレーづくり

完全に余談です。直接は関係無いので、事前準備まで読み飛ばしもOKです⇒読み飛ばす

前述の通り、カレー調理後に最もポケモンが出やすいのは「マホミル級」なのですが、作中で無限に買うことができる「きのみ(クラボのみ・モモンのみ・オレンのみ・カゴのみ)」で「マホミル級」を出すのは結構シビアです。

具体的には、6個以上のきのみを入れて、一切のミスなく上手に調理できれば「マホミル級」になります。火起こしと真心はともかく、かき混ぜがかなり難しいです。

f:id:tangential_star:20210102115521g:plain
f:id:tangential_star:20210102115539g:plain
f:id:tangential_star:20210102115602g:plain
「あおいで!」「まぜて!」「まごころ こめて!」美味しいカレーが出来上がる

一応、食材屋でごくまれに購入できる2200円食材「しっぽのくんせい」「ゆでタマゴ」「フサパック」「モーモーチーズ」や、マックスレイドバトルでまれに手に入る「キョダイパウダー」、ワイルドエリアで拾える「ながねぎ」を使うと、完全放置(無操作)でも「マホミル級」が確定しますが、入手が困難だったり、試行回数を考えるとコスパが悪かったりします。

また、150円食材や400円食材、950円食材では「マホミル級」を幾分作りやすくなりますが、こちらは完全放置というわけには行きません。

なので、私にて色々試したり考えたりした結果、食材は入れず、調理は超一流のシェフ(Arduino Leonardo)におまかせすることにしました

f:id:tangential_star:20210102105423p:plain
f:id:tangential_star:20210102105432p:plain
まれに食材屋で2,200円の食材が販売される。これらを使うと「マホミル級」以上が確定

 

手持ちときのみを揃える(事前準備)

「カレーのあかし」を持ったポケモンを効率的に呼ぶためには、事前の準備として、「最高になついたポケモン6匹」と「大量のきのみ」が必要です。

なつき度(なかよし度)を最大ランクまで上げるには、キャンプでの触れ合いが不可欠です。カレーを沢山作って一緒に過ごしてください。なお、性格に合ったカレーの味だとなかよし度が上がりやすいそうです。それぞれのポケモンの好みに合わせてカレーを作ってあげてください。

f:id:tangential_star:20210102121606p:plain
f:id:tangential_star:20210102121615p:plain
ナックルシティ民家の少年が教えてくれる。なつき度MAXなら「なかよしリボン」も貰える
f:id:tangential_star:20210102123917g:plain
f:id:tangential_star:20210102124425g:plain
話しかけた時に5つのハートがでたり、戦闘時にチラッとコチラを見たらなかよし度MAXだ

続いて、きのみの調達です。

前述の通り平均して7調理ごとに1回しかポケモンには会えない上に、1回毎に少なくとも6個単位できのみを消費するため、大量のきのみが必要になります。地道に木を揺らしていくのは手間暇かかるため、余りがちなお金で無制限にきのみを購入できるブラッシータウンの「きのみショップ」を利用します。DLCを購入している人はヨロイじまのきのみショップでもOKです(ヨロイじまだと4種類から買えます)。

1個80円で買えるので、「クラボのみ」を999個買ってください。8万円を切ります。

買ったきのみは「リュックの一番上」においてください。クラボのみはきのみの種類順で1番になるのでリュックの一番上に来ているはずですが、もし一番上じゃなかったら適宜並び替えてください。

※「クラボのみ」でなくても大丈夫です。本プログラムは「モモンのみ」「オレンのみ」「カゴのみ」のいずれも動作確認できています。無論、「リュックの一番上」においてください。

f:id:tangential_star:20210102122035p:plain
f:id:tangential_star:20210102122044p:plain
ブラッシータウンかヨロイじまの「きのみショップ」で購入できる。999個でも79920円だ


欲しいポケモンが出てくる道路に行こう (準備)

カレー作り後に現れるポケモンは、シンボルエンカウントではなく、草むら・水上で「!」と共に出現するポケモンから選ばれます。また、前述の通りワイルドエリアやDLCの島々では出現しません。これらを勘案して、自分の欲しいポケモンを図鑑の分布などを見ながら探してください。

なお、本記事ではモルペコが欲しいのでルートナイントンネルの右側の9番道路(=スパイクタウンはずれ)に行きます。 

f:id:tangential_star:20210102134430p:plain
f:id:tangential_star:20210102133513j:plain
「カレーのあかし」を持ったポケモンの出現場所について
左:「ポケモン徹底攻略」より画像引用・改変     右:だま氏のTwitter投稿@monst_dama)より画像引用
f:id:tangential_star:20210102132327p:plain
f:id:tangential_star:20210102121233p:plain

「モルペコ」を狙うには、なつき度MAXの6匹を連れて「スパイクタウンはずれ」に行こう

 

プログラムの使い方

下記が必要な条件です。特に重要な準備については前述の通りです。その他は、普通にプレイしていれば満たしているはずですが、念の為合わせて確認してください。

  1. なかよし度MAX のポケモン6匹が手持ちにいる
  2. 充分な量の「きのみ(クラボのみ999個など)」がリュックの一番上にある
  3. 食材を1つ以上持っている(一切消費しませんが、食材欄が空っぽだとうまく動きません)
  4. 充分な量の「モンスターボール」「スーパーボール」「ハイパーボール」いずれかを持っている(これらのいずれかにしか入りません。原則としてモンスターボールが優先されます)
  5. ボックスが空いている

これらを満たしたら、メニューから「ポケモンキャンプ」を開いてください。

そして、ポケモンキャンプが開いたら、手動で一度Xボタンを押し、カーソルを「料理」に合わせてからBボタンでメニューを閉じてください(重要)。

この状態でArduino Leonardoを挿したら、あとは勝手に調理をしてくれます。調理後、10秒ほどかけて自動でキョロキョロしてくれるので、目当てのポケモンがいたらArduino Leonardoを抜いてください。

f:id:tangential_star:20210102141132p:plain
f:id:tangential_star:20210102141141p:plain
事前準備の上、目的の道路に着いたら「ポケモンキャンプ」を開こう
f:id:tangential_star:20210102141454p:plain
f:id:tangential_star:20210102141503p:plain
キャンプでXボタンを押した時に、カーソルが「料理」に合う状態にしてからBを押そう

 

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

ポケモンキャンプでカーソルが「料理」に合う状態でメニューを閉じてからArduinoを挿すだけの簡単設計です。全自動で「調理⇒調理後キョロキョロ」をひたすら繰り返してくれます。目当てのポケモンが見つかったら引っこ抜いてください。

2021/6/9追記:ポケモン捕獲までの完全自動化に成功しました。合わせてご参考ください⇒【Arduino自動化09ex】「カレーのあかし」持ちポケモンの完全自動厳選【キャンプでカレー】

 

所有する「クラボのみ」の数(1~999)と、1回の調理で消費するきのみの量(1~10)をプログラム中でそれぞれ「CHERI_BERRY」「USING_BERRY」で指定することができます。

#define CHERI_BERRY (999) // 「クラボのみ」の所有数
#define USING_BERRY (6) // 1回の調理で何個のきのみを利用するか

残りの量が消費予定量を上回った場合にプログラムは自動で止まるように設計していますので、手持ちのきのみの個数を勘案の上、必要に応じて書き換えて使ってください。

なお、「USING_BERRY」の数ですが、5個以下だと確定で「ソーナンス級」になります。「マホミル級」のカレーを作るには、最低でも6個は必要です。また、6個にしていても、20回くらいに1回くらいの割合で何故かソーナンス級になることがあります。理由は分かりませんが、なんとなく心配な人や、試行回数よりも精度を求めるという人は「10個」にしてください。一応、筆者としては「6」または「10」のいずれかを推奨します。

それではソースコードです。

/* 
 *  全自動カレー作りc⌒っ.ω.)っ 
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h>

#define HOLDTIME (95) // 1回のキー入力の長押し時間
#define KAKIMAZE_CYCLE (435) // かき混ぜ時周期(1回転の必要ミリ秒)
#define RENDA_CYCLE (150) // 連打のサイクル(1回仰ぐのにかかるミリ秒)
#define CHERI_BERRY (999) // 「クラボのみ」の所有数(これをオーバーしないように止まります)
#define USING_BERRY (6) // 1回の調理で何個のきのみを利用するか
 // ↑6,7,10で「マホミル級」検証済。1,5は「ソーナンス級」検証済。
 // メモ:6個の時、極稀にソーナンス級になることがあった。理由は不明。心配な人は10で。
#define AIJO_BASETIME 3200 // 愛情を込めるタイミング
#define AIJO_OFFSET 60 // きのみの数でズレる単位時間(ミリ秒)

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

int CheriBerryNum = 0;

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

void loop() {
  int i=0;
  unsigned long int current_time=0;
  unsigned long int start_time=0;
  unsigned long int temp_time=0;
  float holdA = 0;
  int isholding = 0;
  float temp_deg = 0;
  int isfirsttime =0;
  int aijo_kome = AIJO_BASETIME+AIJO_OFFSET*USING_BERRY;

  if(CheriBerryNum < USING_BERRY){
    // 残りの「クラボのみ」がUSING_BERRY個未満ならプログラムをストップ
    for(;;) delay(1000);  
  }
  
  // メニューを開く
  PushKey("X",    HOLDTIME, 1000);
  // 料理を選択
  PushKey("A",    HOLDTIME, 750);
  // 「料理を始める」
  PushKey("A",    HOLDTIME, 2500);
  // 食材⇒いれない
  PushKey("A",    HOLDTIME, 350); // 空白のA
  PushKey("+",    HOLDTIME, 550);
  PushKey("A",    HOLDTIME, 350); // 空白のA
  // きのみ⇒「クラボのみ(一番上)」USING_BERRY個
  #if(USING_BERRY == 10)
    PushKey("A",    HOLDTIME, 550);
    PushKey("Down", HOLDTIME, 550);
    PushKey("A",    HOLDTIME, 1500); // クラボのみが10個まな板の上に乗るのを待つ
  #else
    PushKey("A", HOLDTIME, 550);
    for(i=0;i<USING_BERRY-1;i++)PushKey("UP",HOLDTIME, 150);
    PushKey("A", HOLDTIME, 180*USING_BERRY);
    PushKey("+", HOLDTIME, 350); // +で材料を決定
  #endif
  // A押してから確実に5秒ジャストで引き渡し
  for( start_time=millis(), current_time=start_time,isfirsttime=1 ; current_time - start_time < 5000 ; current_time=millis() ){
    // ループ初回のみAを押す
    if(isfirsttime){
      // 選んだきのみで始めますか?⇒はい
      PushKey("A", 100, 300);
      isfirsttime=0;
    }
  }
  // 火起こし「21秒」
  for( start_time=millis(), current_time=start_time, isholding=0 ; current_time - start_time < 21000 ; current_time=millis() ){

     // RENDA_CYCLEミリ秒ごとにAを押す
     temp_time = (current_time - start_time) % (RENDA_CYCLE+1); // 経過ミリ秒のRENDA_CYCLE剰余を計算
     holdA = (float)temp_time / (float)RENDA_CYCLE * 100.0; // 剰余から100%に変換
     if( holdA <= 35.0 ){ // RENDA_CYCLEの3.5割の時間はA押し、それ以外はAを離す
       if(!isholding){
         SwitchControlLibrary().PressButtonA();
         isholding = 1;
       }
     }else{
       if(isholding){
         SwitchControlLibrary().ReleaseButtonA();
         isholding = 0;
       }
     }
  }
  if(isholding) SwitchControlLibrary().ReleaseButtonA();

  // かき混ぜ「18秒」
  for( start_time=millis(), current_time=start_time ; current_time - start_time < 18000 ; current_time=millis() ){
     // KAKIMAZE_CYCLEミリ秒ごとに1周スティック回転
     temp_time = (current_time - start_time) % (KAKIMAZE_CYCLE+1); // 経過ミリ秒のKAKIMAZE_CYCLE剰余を計算
     temp_deg = (float)temp_time / (float)KAKIMAZE_CYCLE * 360.0; // 剰余から360°角度に変換
     TiltLeftStick( (int)temp_deg, 1.0, 0, 0); // 経過時間とKAKIMAZE_CYCLEの周期に応じた角度に倒す
  }
  TiltLeftStick( 0, 0.0, 0, 0); // スティックを初期位置に戻す

  // 愛情込め~配膳「20秒」
  for( start_time=millis(), current_time=start_time,isfirsttime=1 ; current_time - start_time < 20000 ; current_time=millis() ){
    // 愛情込めは3.8秒
    temp_time = (current_time - start_time);
    if( temp_time > aijo_kome && isfirsttime==1){ // aijo_komeミリ秒を超えたタイミングで愛情込め
      if(isfirsttime) PushKey("A", 100, 300); // 愛情込め
      isfirsttime=0;
    }
  }
  PushKey("A", 100, 300); // 配膳されたカレーを眺めた後のA
  // もぐもぐ~マホミル級のおいしさ「13秒」
  delay(13000); // カレー実食中・・・
  PushKey("A", 100, 2000); // フィールドに戻る

  // あたりを見回してカレー好きのポケモンがいないか確認(目視で確認してね!)
  TiltLeftStick( 90, 0.5, 2000, 1000); // 右側を見る。
  TiltLeftStick( 270, 0.5, 4000, 1000); // 左側を見る。
  TiltLeftStick( 90, 0.5, 2000, 1000); // 右側を見る。
  TiltLeftStick( 0, 0.0, 0, 0); // スティックを初期位置に戻す

  // 3秒待ったら次のカレーを作ります。
  delay(3000);
  CheriBerryNum -= 10; // 手持ちの「クラボのみ」個数を10個減らす

}

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:20210102002716g:plain
f:id:tangential_star:20210102102901g:plain
左:ループの様子。 右:ポケモンを見つけたらArduinoを抜いてコントローラーを挿そう


あとがき

改めまして、新年あけましておめでとうございます(執筆:2021年1月2日)。昨年の11月にこのブログを始めて、なんとか更新を続けることができています。

アクセス数も順調に伸びてきていて、時には「はてなスター」をいただいたり、読者が増えたり、コメントで「マイコン導入のきっかけになった」と報告をいただいたり、 など、まだまだ駆け出しの当ブログですが、皆様にモチベーションを支えられて更新が続けられているなぁ、と感じます。これからもどうぞ応援のほどよろしくお願いいたします。

 

さて、今回はポケモンキャンプの「カレーづくり自動化」にフォーカスを当てた記事でしたが、やはり自分の思ったとおりにプログラムが動くのは気持ちが良いものですね。今回のプログラムは試行錯誤しながら作ったので、達成感もひとしおでした。

特に、自分がスティック入力をしていないのにカレーが自動でかき混ぜられている様子を見るのが楽しかったです。

f:id:tangential_star:20210102115521g:plain
f:id:tangential_star:20210102115539g:plain
(再掲)全自動で「火起こし」「かき混ぜ」をする様子

前々回の記事では、スティック入力に対応した雛形をつくりましたが、1方向に倒し続ける動作はもちろん、回転させるなどの一連の動きをコントロールできるのは、より人間の作業に近いものを扱えるようで嬉しく思いますね。

もちろん、マクロ対応コントローラーでは絶対真似できない芸当ですし、人間でもこんなに正確に操作はできないので、今回の例はまさにArduino Leonardoならではの自動化とも言えると思います(言い過ぎかな)。

 

話が逸れましたが、以降も精進して参りますので今後ともどうぞよろしくお願いいたします。

 

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

 

2021/6/9追記:ポケモン捕獲までの完全自動化に成功しました。合わせてご参考ください⇒【Arduino自動化09ex】「カレーのあかし」持ちポケモンの完全自動厳選【キャンプでカレー】 

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

次回記事:【Arduino自動化10】きのみ・ぼんぐり自動回収

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

 

おまけ(パッチルドンのはなみず)

この記事の中のどこにも入れられなかったのですが、どうしても紹介したかったことが1つあったので追伸として。

 

パッチルドンといえばいつも凍えていて鼻水を垂らしていますよね。

でも、カレーを食べている時だけ、パッチルドンの震えも鼻水も止まっているのです。

上でさんざんGIF動画の中に映っていましたが、気づきましたか?

 

f:id:tangential_star:20210102153141g:plain

カレーをもぐもぐ食べている時だけ、パッチルドンのはなみずと震えが止まっている

 

以上です。今年もよろしくお願いいたします。c⌒っ.ω.)っ

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

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

今回は、「バトルタワー」自動周回で、BPを稼ぐArduino Leonardo自動化の記事です。

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

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

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

 

概要

本記事では、シュートシティの「バトルタワー」自動周回を実装します。

f:id:tangential_star:20201229104124p:plain
f:id:tangential_star:20201229144836g:plain
バトルタワーを自動周回(1周あたり3分~6分ほど)。1周ごとに2BP貰える

目次です。

 

経緯

バトルタワー周回という「いまさら」感が強いタイミングでの記事化となりますが、実は、当ブログのアクセス状況を確認すると、ダイマックスアドベンチャーの自動周回記事が(アクセス数も検索流入も)大変盛況なのです。

どうやら、従来のマクロ対応コントローラーでできることをマイコン(本ブログだとArduino Leonardo)に対応させたいという需要は一定数あるらしく、それならこのブログでも取り上げる価値があるかな?と思ったので記事化しました。

BP(バトルポイント)は、お金やワットとは引き換えられないもの(いじっぱりミントなど)も多いため、そういう意味でも需要はあると思います。

 

この記事のスタンス

本記事では、下記の前々回・前回(第6回・第7回)の記事に引き続き、マクロ対応コントローラーの代替用の雛形をベースにArduino Leonardo用にプログラムを書いていきます。例のごとく、入力するコマンド列が予め分かっているものを活用します。

なお、本ブログで扱うコマンド列は、私が考案したものではありません。ダイマックスアドベンチャーの記事でも紹介した、あずき氏によるものです。当該ブログ記事を下記にて引用します。合わせてご参考ください。

azukiss.hatenablog.com

 

バトルタワー自動周回のやりかた

バトルタワーの自動周回には、以下の条件を満たしている必要があります。

  1. バトルタワー「シングルバトル」ランクが「マスターボール級」であること
  2. 自動周回用のパーティーを準備していること

1のマスターランク到達は前提ですが、今作では禁止伝説級のポケモンが使用できるのでゴリ押しで攻略可能です。1時間ほどでマスターランクに到達できると思います。

2の自動周回用パーティーですが、

の3匹を準備してください。なお、それぞれのPPは最大値まで増やしておきましょう。

自前で準備が難しければレンタルチーム(要Nintendo Online加入)でも可能です(参考チームID:0000 0000 5MJ4 0K)。

f:id:tangential_star:20201229133747p:plain
f:id:tangential_star:20201229133714p:plain
自動周回は、シングルバトルが「マスターボール級」前提。まだの人はまず攻略しよう
f:id:tangential_star:20201229130956p:plain
f:id:tangential_star:20201229130109p:plain
メニューの「VS」からチームのレンタルが可能(要Nintendo Online加入)。

コマンド列ですが、原則としてAボタン連打で攻略していき、ポケモン入れ替えなどのために「↑」や「B」を押す、というイメージです。下記を御覧ください。

 

自動周回のコマンド入力列(あずき氏のブログ記事より引用・一部改変)

1 2 3 4 5 6 7 8
9 10 11 12 13 14 15,16
R,R(連打)

※指定が無い部分のキー入力間隔は約1.5秒

要するに、この16コマンドをひたすら繰り返すことで、バトルタワーを延々と攻略してBPを稼いでくれるということです。すごいですね。

 

自動化コマンドの実装

前回の記事で紹介した雛形・関数の要領で、前述の16コマンドを入力していきます。雛形は下記テキストボックスからコピペも可能です。今回はスティック入力が無いので、「PushKey("入力キー", 長押しミリ秒, 待機ミリ秒 ); 」を16行並べるだけです。

 

Arduino Leonardo自動化用 プログラム雛形

 

前述のコマンド列を関数に当てはめると下記の様になります。この雛形の中に下記「loop(){」と「}」の間16行を埋め込むだけでプログラムになります。

こちらは説明用に抜粋したプログラムの一部です。プログラム全文は⇒コチラをクリックして読み飛ばす

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

void loop() {
  // ↓ここに繰り返すコマンド列を入れよう
  PushKey("A",    HOLDTIME, 1500); // 1: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 2: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 3: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 4: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("UP",   HOLDTIME, 1500); // 5: ↑HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 6: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 7: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("UP",   HOLDTIME, 1500); // 8: ↑HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 9: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 10:A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 11:A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 12:A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("B",    HOLDTIME, 1500); // 13:B HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("UP",   HOLDTIME, 1500); // 14:↑HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("R",    HOLDTIME, 150 ); // 15:R HOLTIMEミリ秒長押し ほぼ待機なし
  PushKey("R",    HOLDTIME, 150 ); // 16:R HOLTIMEミリ秒長押し ほぼ待機なし
  // ↑ここに繰り返すコマンド列を入れよう
}

なお、今回は15個目のRと16個目のRの間を連打するので、それぞれの待機時間を150ミリ秒(充分短い時間)にしています。なお、この間が極端に短すぎる(例えば10ミリ秒などに設定する)と、Switch側がボタンの押下状態を認識できずコマンドミスすることがあるようです。注意しましょう。

 

プログラムの使い方

前述の通り、攻略用3匹のみのパーティーを準備の上、シュートシティの上側にあるバトルタワーへ行き、受付の前でArduino Leonardoを差し込むだけです。
なお、初回は「手持ち」にカーソルが合っていると思いますので、上記のレンタルチームを活用している人は、手動でそちらにカーソルをあわせておいてください。

f:id:tangential_star:20201229143158p:plain
f:id:tangential_star:20201229133747p:plain
f:id:tangential_star:20201229133714p:plain
f:id:tangential_star:20201229143220p:plain
攻略用チームが選ばれるように準備の上、バトルタワーの受付前でArduinoを差し込む。

 

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

雛形は前回の記事と同一です。

/* 
 *  バトルタワー自動周回
*/

/* マクロコンのコマンドをソースコード化するための雛形【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() {
  // ↓ここに繰り返すコマンド列を入れよう
  PushKey("A",    HOLDTIME, 1500); // 1: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 2: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 3: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 4: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("UP",   HOLDTIME, 1500); // 5: ↑HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 6: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 7: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("UP",   HOLDTIME, 1500); // 8: ↑HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 9: A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 10:A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 11:A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("A",    HOLDTIME, 1500); // 12:A HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("B",    HOLDTIME, 1500); // 13:B HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("UP",   HOLDTIME, 1500); // 14:↑HOLTIMEミリ秒長押し 1.5秒待機
  PushKey("R",    HOLDTIME, 150 ); // 15:R HOLTIMEミリ秒長押し ほぼ待機なし
  PushKey("R",    HOLDTIME, 150 ); // 16:R HOLTIMEミリ秒長押し ほぼ待機なし
  // ↑ここに繰り返すコマンド列を入れよう
}

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:20201229144836g:plain
f:id:tangential_star:20201229145153p:plain
自動周回で手に入れたBPで「ようきミント」「こだわりスカーフ」などを交換しよう


あとがき

本記事では、Arduino Leonardoを使ったバトルタワー自動周回(BP稼ぎ)について紹介をしました。

パーティーをいくつも作っていると「こだわりメガネ」「こだわりスカーフ」が足りなくなったり、不意の事故で「きあいのタスキ」「ふうせん」などを野生戦で消費してしまったりしますよね。ほかにも、育成用に欠かせない「ようきミント」「いじっぱりミント」なども不足しがちです。このあたりの道具が引き換えられるのはありがたいですね。

 

今回のバトルタワー然り、前回のトーナメント然り、前々回のダイマックスアドベンチャー然り、ポケモンには対人戦の準備に関わる「周回」が切っても切り離せないですね。無論、こういったエンドレスに遊べるコンテンツを通してやりこみや対人戦前の調整をするのが目的になるのでしょうが、アイテム回収を目的にすると少しツライものがあります。この観点において、マクロ対応コントローラーやマイコンは、各人のゲームとの関わり方の選択肢を増やしてくれるので、うまく活用していきたいものですね。

 

本記事が皆様のポケモン自動化ライフの一助となることを願っています。

 

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

 

前回記事:

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

前々回記事:

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

次の記事:

【Arduino自動化09】ポケモンキャンプでカレー自動調理【カレーのあかし】

導入記事:

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

 

【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開発環境の導入