こんにちは、ますたーです。
今回は、ポケモンキャンプで「カレー」を自動で調理し、時々遊びに来る「カレーのあかし」を持ったポケモンを自動で集め続けるArduino自動化です。
前回記事(Arduino自動化09)の改良版となります。もちろん、本記事だけでも読めるように書きますが、前回記事も個人的に気に入っている記事なのでそちらも合わせて是非読んでほしいです。
なお、Arduino Leonardo自動化の導入と、機材構成については導入記事をご覧ください。
導入記事:【Arduino自動化01】Arduino開発環境の導入
※本ブログに初めてお越しの方は「本ブログについて」もぜひ、ご覧ください。
概要
本記事では、ポケモン剣盾の「ポケモンキャンプ」の「カレー」づくりを自動で行います。さらに、調理後には左右を確認します。より具体的には、「クラボのみ」を6個ずつ消費(食材不要)して、マホミル級のカレーを作り、カレー調理後には広場にいるポケモンに話しかけてくれます。これらを完全放置で行えます。
すなわち、自動で「カレーのあかし」を持ったポケモンを仲間にしてくれます。
目次です。ソースコードのみ欲しいという方は⇒読み飛ばす。
カレーのあかしを持ったポケモンについて
まずは、カレーを自動で調理する目的についてのおさらいです。詳細は前回記事(Arduino自動化09)に載っていますが、こちらでも改めて説明します。
ポケモンキャンプでカレーを作ると時々、野生ポケモンがキャンプに遊びに来ることがあります。遊びに来たポケモンに話しかけると仲間にすることができます。
ポケモンキャンプに来たポケモンは「カレーのあかし」というリボンを確定で持っており、このリボンを選択することで「カレーずきな」という二つ名を付けることができます。従来の野生ポケモン(あかしを持つ確率は2%~4%)と異なり、キャンプで仲間になるポケモンは確定でリボンを持っていることから、いわゆる「証厳選」の中でも比較的簡単な部類と言われています。
なお、ポケモンキャンプでカレー作りをすれば、どこでも野生ポケモンが遊びに来てくれるわけではありません。出現するポケモンの種類・場所はあらかじめ決まっています。
具体的には、ワイルドエリア・DLCの島などを除く、道路・ダンジョン(ルミナスメイズのもり・ガラルこうざんなど)で出会うことができます。また、出会えるポケモンは、その場所でランダム出現するポケモン(シンボルとして出現しているポケモンではなく「!」のマークが出て出現するポケモン)です。
出現ポケモンと場所については前回記事(Arduino自動化09)でも紹介した通り、だま氏がまとめた画像(下記参照)などで確認ができます。
なお、手持ちの「なかよし度」と、作ったカレーの出来栄え(ソーナンス級・マホミル級など)によって、カレー調理後に野生ポケモンの遊びに来てくれる確率が変動します。
最も効率的な組み合わせは、「手持ち6匹のなかよし度がMAX」かつ「カレーのグレードが『マホミル級』」の2つを満たした時で、その確率は「1/7(≒14.28%)」です。
プログラムの使い方(事前準備)
早速プログラムの使い方の紹介です。ソースコードは後述します。
まずは、準備として下記を確認してください。
- 手持ちが6匹埋まっている
- 充分な量の「きのみ(クラボのみ999個など)」がリュックの一番上にある
- 食材を1つ以上持っている(一切消費しませんが、食材欄が空っぽだとうまく動きません)
- 充分な量の「モンスターボール」「スーパーボール」「ハイパーボール」いずれかを持っている(これらのいずれかにしか入りません。原則としてモンスターボールが優先されます)
- ボックスが空いている
これらを満たしたら、メニューから「ポケモンキャンプ」を開いてください。
そして、ポケモンキャンプが開いたら、手動で一度Xボタンを押し、カーソルを「料理」に合わせてからBボタンでメニューを閉じてください(重要)。
上記5つを満たしていれば、準備OKです。Arduinoにプログラムを書き込んでSwitchに差し込むだけでカレーづくり&調理後キョロキョロ見回しながら話しかけて野生ポケモンを仲間に加えてくれます。
ちなみに、手持ちポケモンがなかよし度MAXだと、野生ポケモンが遊びに来てくれる確率が上がりますが、なかよし度はカレーを作ることで自動的に上がっていきますのでご安心ください。つまり、初めてカレー作りに挑戦する人や、「カレーのあかし」厳選初心者の方については、きのみとボールを準備して何も考えずに手持ち6匹埋めてキャンプするだけでOKです。
動作検証:15時間以上破綻なくプログラムが稼働しました!
ここで1つ動作報告です。読み飛ばしOKです⇒読み飛ばす。
作ったプログラムのデバッグ作業も兼ねて、リュックに「クラボのみ」「オレンのみ」「カゴのみ」「モモンのみ」をそれぞれ999個ずつ詰め込み、耐久デバッグ(SwitchにArduinoを挿して動作が止まるまでひたすら放置)をしました。
結果、実に15時間49分もの間、一切のループ破綻なく、カレーを作り続けることができました(画面録画の動画容量はなんと19.2GB!)。
※検証のため、プログラム中の定数「CHERI_BERRY」は「(999*3)」に書き換えて定義しています。一応、プログラムでの想定では999を上限にしていましたが、きちんと動きました
この検証のために使用したきのみは合計2940個、仲間になったポケモンは61匹でした。
すなわち、期待値として、1時間あたり3.85匹と仲間になれることが分かります。なお、手持ちのきのみは先頭4行を999個で埋めて、手持ちポケモンは6匹ともなかよし度MAXにしてあります。また、1回の調理に使うきのみは6個にしました。
注意すべき点として、コーディングの都合上、一部、プログラム上では「B」ボタンを使用しています。
稀(15匹くらいに1匹くらいの割合?)に遊びに来たポケモンを仲間に加えなかったり、極稀(筆者環境では述べ50時間の検証中1回だけ発生)に途中でキャンプを終了してしまったりすることがあり得ます。
あくまでも、決められたキー操作を自動で繰り返しているだけ、という点には重々承知の上、自己責任にてご利用くださいませ。
前回(Arduino自動化09)のソースコードと同様、ポケモンキャンプでカーソルが「料理」に合う状態でメニューを閉じてからArduinoを挿すだけの簡単設計です。
2箇所、あらかじめプログラムを必要に応じて書き換えてください。上限とする「きのみの数(1~999)」と、1回の「調理で消費するきのみの量(1~10)」をそれぞれ「CHERI_BERRY」「USING_BERRY」で指定することができます。
#define CHERI_BERRY (999)
#define USING_BERRY (6)
残りの量が消費予定量を上回った場合にプログラムは自動で止まるように設計しています。なお、非推奨ですが、耐久デバッグのためにCHERI_BERRYを(999*3)で利用し、15時間稼働させた実績もあるので、きのみ3~4種類をそれぞれ999個ずつお持ちであれば、そういった長時間前提の利用も可能かと思います。
いずれにしても、自己責任にて利用ください。
#include <SwitchControlLibrary.h>
#define HOLDTIME (95)
#define KAKIMAZE_CYCLE (435)
#define RENDA_CYCLE (150)
#define CHERI_BERRY (999)
#define USING_BERRY (6)
#define AIJO_BASETIME 3200
#define AIJO_OFFSET 60
#define HANASHIKAKE_CYCLE (125)
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() {
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){
for(;;) delay(1000);
}
PushKey("X", HOLDTIME, 1000);
PushKey("A", HOLDTIME, 750);
PushKey("A", HOLDTIME, 2500);
PushKey("A", HOLDTIME, 350);
PushKey("+", HOLDTIME, 550);
PushKey("A", HOLDTIME, 350);
#if(USING_BERRY == 10)
PushKey("A", HOLDTIME, 550);
PushKey("Down", HOLDTIME, 550);
PushKey("A", HOLDTIME, 1500);
#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
for( start_time=millis(), current_time=start_time,isfirsttime=1 ; current_time - start_time < 5000 ; current_time=millis() ){
if(isfirsttime){
PushKey("A", 100, 300);
isfirsttime=0;
}
}
for( start_time=millis(), current_time=start_time, isholding=0 ; current_time - start_time < 21000 ; current_time=millis() ){
temp_time = (current_time - start_time) % (RENDA_CYCLE+1);
holdA = (float)temp_time / (float)RENDA_CYCLE * 100.0;
if( holdA <= 35.0 ){
if(!isholding){
SwitchControlLibrary().PressButtonA();
isholding = 1;
}
}else{
if(isholding){
SwitchControlLibrary().ReleaseButtonA();
isholding = 0;
}
}
}
if(isholding) SwitchControlLibrary().ReleaseButtonA();
for( start_time=millis(), current_time=start_time ; current_time - start_time < 18000 ; current_time=millis() ){
temp_time = (current_time - start_time) % (KAKIMAZE_CYCLE+1);
temp_deg = (float)temp_time / (float)KAKIMAZE_CYCLE * 360.0;
TiltLeftStick( (int)temp_deg, 1.0, 0, 0);
}
TiltLeftStick( 0, 0.0, 0, 0);
for( start_time=millis(), current_time=start_time,isfirsttime=1 ; current_time - start_time < 20000 ; current_time=millis() ){
temp_time = (current_time - start_time);
if( temp_time > aijo_kome && isfirsttime==1){
if(isfirsttime) PushKey("A", 100, 300);
isfirsttime=0;
}
}
PushKey("A", 100, 300);
delay(13000);
PushKey("A", 100, 1500);
TiltLeftStick( 270, 1.0, 2000, 0);
TiltLeftStick( 90, 0.9, 0, 0);
for( start_time=millis(), current_time=start_time, isholding=0 ; current_time - start_time < 2.5*1000UL ; current_time=millis() ){
temp_time = (current_time - start_time) % (HANASHIKAKE_CYCLE+1);
holdA = (float)temp_time / (float)HANASHIKAKE_CYCLE * 100.0;
if( holdA <= 35.0 ){
if(!isholding){
SwitchControlLibrary().PressButtonA();
isholding = 1;
}
}else{
if(isholding){
SwitchControlLibrary().ReleaseButtonA();
isholding = 0;
}
}
}
if(isholding) SwitchControlLibrary().ReleaseButtonA();
TiltLeftStick( 0, 0, 0, 0);
delay(1000);
PushKey("A", HOLDTIME, 1100);
PushKey("Down", HOLDTIME, 700);
PushKey("Down", HOLDTIME, 700);
PushKey("A", HOLDTIME, 1200);
TiltLeftStick( 90, 1.0, 2000, 0);
TiltLeftStick( 270, 0.9, 0, 0);
for( start_time=millis(), current_time=start_time, isholding=0 ; current_time - start_time < 2.5*1000UL ; current_time=millis() ){
temp_time = (current_time - start_time) % (HANASHIKAKE_CYCLE+1);
holdA = (float)temp_time / (float)HANASHIKAKE_CYCLE * 100.0;
if( holdA <= 35.0 ){
if(!isholding){
SwitchControlLibrary().PressButtonA();
isholding = 1;
}
}else{
if(isholding){
SwitchControlLibrary().ReleaseButtonA();
isholding = 0;
}
}
}
if(isholding) SwitchControlLibrary().ReleaseButtonA();
TiltLeftStick( 0, 0, 0, 0);
PushKey("A", HOLDTIME, 150);
PushKey("A", HOLDTIME, 150);
PushKey("A", HOLDTIME, 150);
PushKey("A", HOLDTIME, 150);
PushKey("A", HOLDTIME, 150);
delay(1000);
PushKey("A", HOLDTIME, 1100);
PushKey("Down", HOLDTIME, 700);
PushKey("Down", HOLDTIME, 700);
PushKey("A", HOLDTIME, 1600);
delay(1600);
PushKey("B", HOLDTIME, 500);
PushKey("B", HOLDTIME, 500);
delay(2000);
CheriBerryNum -= USING_BERRY;
}
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){
if(strlen(keyname)==1){
switch(keyname[0]){
case 'A': case 'a':
SwitchControlLibrary().PressButtonA(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonA(); delay(delaytime);
break;
case 'B': case 'b':
SwitchControlLibrary().PressButtonB(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonB(); delay(delaytime);
break;
case 'X': case 'x':
SwitchControlLibrary().PressButtonX(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonX(); delay(delaytime);
break;
case 'Y': case 'y':
SwitchControlLibrary().PressButtonY(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonY(); delay(delaytime);
break;
case 'L': case 'l':
SwitchControlLibrary().PressButtonL(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonL(); delay(delaytime);
break;
case 'R': case 'r':
SwitchControlLibrary().PressButtonR(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonR(); delay(delaytime);
break;
case 'H': case 'h':
SwitchControlLibrary().PressButtonHome(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
break;
case '+': case 'p': case 'P':
SwitchControlLibrary().PressButtonPlus(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonPlus(); delay(delaytime);
break;
case '-': case 'm': case 'M':
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':
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':
SwitchControlLibrary().MoveHat(2); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
break;
case 'l': case 'L':
SwitchControlLibrary().MoveHat(6); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
break;
case 'u': case 'U':
SwitchControlLibrary().MoveHat(0); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
break;
case 'd': case 'D':
SwitchControlLibrary().MoveHat(4); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
break;
case 'H': case 'h':
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){
delay(holdtime);
SwitchControlLibrary().MoveLeftStick(128,128);
}
if(delaytime>0) delay(delaytime);
return;
}
あとがき
今回の記事では、前回記事(Arduino自動化09)の改良版として、ポケモンキャンプでのカレー作りの自動化に、遊びに来たポケモンを仲間に加える機能を追加しました。
実は、「カレー自動調理」の記事がこのブログの中で執筆当初より根強い人気を誇る記事の一つだったこと、個人的にも本ブログの執筆記事で特に気に入っているものだったこともあり、「いつか絶対、『完全』自動化できるように改良しよう!」と意気込んでいたのでした。2021年1月2日の執筆から約半年後の本日6月9日に、ついにその夢を実現させ、完全自動化に成功しました。
やっぱり、Arduinoを使ったSwitch操作の自動化を考えたときに、スティック操作もボタン操作も余すこと無く活用している本プログラムは、動作する様子を眺めていて楽しいです。
ただ、今回のプログラムは、ポケモンが遊びに来る確率が最大でも1/7と低く、1試行にも2分ほどかかるという超長時間のデバッグを要するため、結構たいへんでした。
とはいえ、このプログラムの実装ができたことで、「『カレーのあかし』持ち色違い厳選」が現実的なものになりました。要するに、「証持ち色違い」という今までのポケモンの厳選の、さらにその上を行く難易度の厳選作業に、完全放置可能というアシストを付けて、足を踏み入れることができます。
このプログラムを使って「カレーのあかし持ち色違いポケモンを捕まえることができた」という人が現れることを願って、あるいは、私がそれに出会えることを願って、本記事を公開させていただきます。
もし、この記事や本ブログが参考になっているよ!という方がいらっしゃいましたら、是非ともコメントやはてなスターを残してもらえると、私自身のやる気とモチベに繋がりますので、引き続きよろしくお願いいたします。
ではではc⌒っ.ω.)っ
前回記事:【Arduino自動化09】ポケモンキャンプでカレー自動調理【カレーのあかし】
導入記事:【Arduino自動化01】Arduino開発環境の導入
他のArduino自動化:ポケモン剣盾Arduino自動化 カテゴリーの記事一覧
YouTubeチャンネル:ますたーの忘備録 - YouTube 【NEW!!】