#0. はじめに
まずは大事な大事な宣伝から。
■自動化するまでを紹介した記事
https://go-go-poke.hateblo.jp/entry/2020/01/24/003421
■ワット自動回収のソースの解説
https://qiita.com/pokezaresu/items/214b79add7b4974dfe68
■化石回収の自動化
https://qiita.com/pokezaresu/items/1fe1f44da7a36bb2b5ac
■願いの塊1個で狙いのポケモンを出す方法
https://go-go-poke.hateblo.jp/entry/2020/02/02/152057
#1.ソース
#include <SwitchControlLibrary.h>
int maxpokecnt = 420; // マジカル交換したいポケモンの数を指定してください。
int pokecnt = 0; // ポケモンの数
// 初期処理 マイコン接続後1番に動きます
void setup(){
// LRボタン押下でコントローラーとして認識させる
SwitchControlLibrary().PressButtonR();
delay(50);
SwitchControlLibrary().ReleaseButtonR();
delay(500);
SwitchControlLibrary().PressButtonL();
delay(50);
SwitchControlLibrary().ReleaseButtonL();
delay(500);
SwitchControlLibrary().PressButtonR();
delay(50);
SwitchControlLibrary().ReleaseButtonR();
delay(500);
SwitchControlLibrary().PressButtonR();
delay(50);
SwitchControlLibrary().ReleaseButtonR();
delay(500);
SwitchControlLibrary().PressButtonL();
delay(50);
SwitchControlLibrary().ReleaseButtonL();
delay(500);
SwitchControlLibrary().PressButtonR();
delay(50);
SwitchControlLibrary().ReleaseButtonR();
delay(2000);
}
// 繰り返し処理 マイコンの接続をやめるまで{}内を繰り返します
void loop() {
if(pokecnt < maxpokecnt){
pokecnt++;
// 開始
ButtonY();
delay(1000);
MoveHatDown();
delay(500);
ButtonA();
delay(2000);
// ポケモン選択
PokeSelect(pokecnt);
delay(500);
ButtonA();
delay(500);
ButtonA();
delay(5500);
ButtonA();
delay(1000);
ButtonA();
delay(1000);
ButtonA();
delay(90000);
// 交換相手検索中...
ButtonY();
delay(23000);
ButtonA();
delay(500);
ButtonA();
delay(500);
ButtonA();
delay(500);
ButtonA();
delay(500);
ButtonA();
delay(500);
}else{
SwitchSleep();
}
}
//Aボタンを押下して離すまで
void ButtonA(){
SwitchControlLibrary().PressButtonA();
delay(50);
SwitchControlLibrary().ReleaseButtonA();
}
//Yボタンを押下して離すまで
void ButtonY(){
SwitchControlLibrary().PressButtonY();
delay(50);
SwitchControlLibrary().ReleaseButtonY();
}
//Rボタンを押下して離すまで
void ButtonR(){
SwitchControlLibrary().PressButtonR();
delay(50);
SwitchControlLibrary().ReleaseButtonR();
}
//ホームボタンを押下して離すまで
void ButtonHome(){
SwitchControlLibrary().PressButtonHome();
delay(50);
SwitchControlLibrary().ReleaseButtonHome();
}
//下を入力して離すまで
void MoveHatDown(){
SwitchControlLibrary().MoveHat(4); // down
delay(50);
SwitchControlLibrary().MoveHat(8); // center
}
//右を入力して離すまで
void MoveHatRight(){
SwitchControlLibrary().MoveHat(2); // right
delay(50);
SwitchControlLibrary().MoveHat(8); // center
}
//上を入力して離すまで
void MoveHatUp(){
SwitchControlLibrary().MoveHat(0); // up
delay(50);
SwitchControlLibrary().MoveHat(8); // center
}
//Switchをスリープにする
void SwitchSleep(){
SwitchControlLibrary().PressButtonHome();
delay(4000);
SwitchControlLibrary().ReleaseButtonHome();
ButtonA();
}
//ポケモン選択
void PokeSelect(int pokecnt){
int cnt; // 1ボックスのポケモンの数
int x; // ボックス横
int y; // ボックス縦
// 操作するボックスのポケモンの数を数える
cnt = (pokecnt - 1) % 30 + 1;
// カーソルの移動する数を決める
x = (cnt -1) % 6;
y = (cnt -1) / 6;
// ボックス移動(右)
if(pokecnt % 30 == 1 && pokecnt != 1){
// 30で割った余りが1の場合、かつポケモンが1匹目でない場合
ButtonR();
delay(500);
}
// カーソル右移動
for(int i = 0; i < x; i++){
MoveHatRight();
delay(50);
}
// カーソル下移動
for(int i = 0; i < y; i++){
MoveHatDown();
delay(50);
}
}
#2.解説
今回も、解説したいから解説します。
前回と同じく、説明不要と判断した部分は省きます。
int maxpokecnt = 420; // マジカル交換したいポケモンの数を指定してください。
int pokecnt = 0; // ポケモンの数
※実際の使い方※
30匹交換したい場合は、maxpokecnt=30と指定します。
他は特にいじる必要はありません。
あ、delay();
の()内の値は自由に変えてください。かなり余分に時間を取っています。
では、今回登場する変数の説明から。
変数名 | 内容 |
---|---|
maxpokecnt | マジカル交換を"する"ポケモンの数 |
pokecnt | マジカル交換を"した"ポケモンの数 |
ネーミングセンスの無さが徐々に露呈しているようで嫌なんですが、maxpokecntはmax=最大値、poke=ポケモン、cnt=数という意味です。
※ちなみに、cntとはcountの略で、何かをカウントする時に(自分は)この変数名を使います。
maxpokecntがそういう意味であれば、pokecntがどういう意味かというとpoke=ポケモン、cnt=数です。
こう考えるとちょっと意味が分かりませんね。
表で書いた通り、交換したポケモンの数を数える変数として宣言しています。
では、実際にこの変数たちがどう使われているのかというと、以下になります。
if(pokecnt < maxpokecnt){
pokecnt++;
// ここにマジカル交換の処理がある。
// 説明のため省略。
}else{
SwitchSleep();
}
マイコンをつなげて、処理が走ると、真っ先にif文の判定が始まります。
このif文は「交換したポケモンの数(pokecnt)が交換するポケモンの数(maxpokecnt)未満ですよね?」っと確認しています。
当然、初回は交換したポケモンは0匹なわけですから、if文の{}内の処理が実行され、マジカル交換の処理が行われるわけです。
そして、マジカル交換の度に1が加算され、交換するポケモンの数だけマジカル交換をしたら、else{}内の処理が実行されるわけです。
今回の場合は、Switchをスリープにするという処理。
前回の化石でもやっている処理ですが、自動化する用がなくなったSwitchをスリープできるのはとてもエコだし、実質的にマイコンのloop処理を抜けられるしで結構気に入ってます。
そして、次が今回のプログラムのキモですね。
ちょ~っとプログラムをかじった人ならさほど難しいものではないんですが、ポケモンの自動化の中では難度の高い方だと思います。
if(pokecnt < maxpokecnt){
pokecnt++;
// ポケモン選択
PokeSelect(pokecnt);
}else{
SwitchSleep();
}
//ポケモン選択
void PokeSelect(int pokecnt){
int cnt; // 1ボックスのポケモンの数
int x; // ボックス横
int y; // ボックス縦
// 操作するボックスのポケモンの数を数える
cnt = (pokecnt - 1) % 30 + 1;
// カーソルの移動する数を決める
x = (cnt -1) % 6;
y = (cnt -1) / 6;
// ボックス移動(右)
if(pokecnt % 30 == 1 && pokecnt != 1){
// 30で割った余りが1の場合、かつポケモンが1匹目でない場合
ButtonR();
delay(500);
}
// カーソル右移動
for(int i = 0; i < x; i++){
MoveHatRight();
delay(50);
}
// カーソル下移動
for(int i = 0; i < y; i++){
MoveHatDown();
delay(50);
}
}
PokeSelect
関数ということで、出ましたよ!関数。
これは変数と同じで、プログラミングしている人が自由に作れて名付けられるものです。
※関数についてはあらかじめ用意されているものや、「ライブラリ」を登録して、他者が作った関数を利用することもあります。
これは自分が作った関数となるわけですが、何をやっているのかというと、名前の通りポケモンの選択をやっています。
マジカル交換でポケモンの選択と言えば、何をやっているか、想像つきますよね?
マジカル交換を行うボックス内のポケモンを選択しています。
具体的には、PokeSelect(pokecnt);
のところで、PokeSelect
関数に「現在交換しているポケモンの数」を渡します。
これは、delay();
の()内に数字を入れるのと同じで、()内の値を関数に渡しています。
プログラムの下部に記載しているPokeSelect
関数側では、受け取った「現在交換しているポケモンの数」を元に処理を行います。
//ポケモン選択
void PokeSelect(int pokecnt){
int cnt; // 1ボックスのポケモンの数
int x; // ボックス横
int y; // ボックス縦
// 操作するボックスのポケモンの数を数える
cnt = (pokecnt - 1) % 30 + 1;
// カーソルの移動する数を決める
x = (cnt -1) % 6;
y = (cnt -1) / 6;
まず、cnt変数に操作するボックスのポケモンの数を入れます。
説明が少し間違っている気がしますが、例えば、交換したポケモンの数が43匹で、次に44匹目のポケモンを選択したいとします。
この場合、1BOX30匹までですから、2BOX目の14匹目を選択しなくてはいけません。
このPokeSelect
関数では、操作する1BOXのことしか考えていないため、14匹目のポケモンを選択できるようにcnt変数へ値(14)を入れたいわけです。
で、ぱっと見計算式が何をやっているのか分からなくて、cntにどういう値を入れているのか分かりませんよね?
これを紐解くためには%
が何を意味しているのかを説明する必要があります。
%
は割った余りを出す数式です。
(例) 5%3の答えは2となります。
※5/3=1余り2
つまり、先程の44匹目を例にしてプログラムの計算式を計算すると、(44 - 1) ÷ 30 の余り +1となります。
43÷30=1余り13となり。
13+1=14となります。
答えは14ですよね?
だから、cntには14匹目という値が入ります。
しかし、直感的にはなぜ初めに-1しているのか分からなくないですか?
自分はすぐにこの謎に気づけず、ちょっとだけ躓きましたw
理由を簡潔に言うと、30匹目を選択したいのに、30%30の答えが30とならないためです。
30や60を30で割った余りは0で、30や60を31で割った余りは30や29となるためです。
予め1を減算することで、1匹目なら余り0((1-1)%30)、30匹目なら余り29((30-1)%30)という状態にしておくことで、後から1を加算するとうまくいくという寸法です。
31匹目でも余り0で、1を加算すれば、1匹目となりますよね?
これで、操作中のBOX内にいる何匹目のポケモンを選択すればよいかが分かりました。
次に知りたい情報は、①右に何匹目のポケモンか。②下に何匹目のポケモンか。です。
// カーソルの移動する数を決める
x = (cnt -1) % 6;
y = (cnt -1) / 6;
①をx変数。②をy変数。として扱っております。
①も②も説明は簡単で、BOX内で横に並ぶポケモンの数が6匹であることから計算しています。
ちょっと説明しにくいので、分からない人はcntに値を代入してなんでxは余りなのか、なんでyは割っているのかを確認してみてください。
ちなみにcntに-1している理由は先程のcntを求めた時と同じ理由です。
でも、後ろの+1がいませんよね?
これはなぜかというと、カーソルの移動回数が理由となります。
BOXが表示された段階で、1匹目(一番左上)のポケモンが選択されている状態です。
その状態で、30匹目のポケモンを選択するために右へ6回移動、下へ5回カーソルを移動させると、30匹目のポケモンを選択することができないからです。
考え方として、1匹目のポケモンが選択されている=既に右に1回、下に1回移動している状態。と考えてもらえれば分かりやすいですかね?
なので、xとyの変数には+1していません。
// ボックス移動(右)
if(pokecnt % 30 == 1 && pokecnt != 1){
// 30で割った余りが1の場合、かつポケモンが1匹目でない場合
ButtonR();
delay(500);
}
先程、選択中のBOXしか考えないといったな?あれは嘘だ。
ここで、31匹目、61匹目と30n+1匹の時にBOXの変更を行っております。
if文の内容は上記の意味であっていて、後ろにくっついている&& pokecnt != 1
は何かというと、1匹目の場合は除くという意味でくっつけています。
この条件だと、1匹目の時でもBOXを変更することになるので、それを回避しているわけです。
// カーソル右移動
for(int i = 0; i < x; i++){
MoveHatRight();
delay(50);
}
// カーソル下移動
for(int i = 0; i < y; i++){
MoveHatDown();
delay(50);
}
ここで、カーソル移動しています。
xとy変数に入れた値の数だけループして、右移動と、下移動を繰り返します。
ちなみに、こんな感じで動きます。
マジカル交換の自動化。
— ウマグざれ (@pokezaresu) February 2, 2020
ポケモンの選択はこんな感じ。※1.2倍速 pic.twitter.com/8UlUZxpDMP
他はまぁ・・・、ポケモンのマジカル交換の動作に合わせてボタンや方向を入力したり、delay();
したりするだけなので、割愛です!
#さいごに
今回はwi-fi通信が絡む自動化ということで、どれだけdelay();
すべきかが分からず大変でした。
最終的には、放置したつもりがうまくいかずに止まっていたよりかは、それなりに時間がかかるけど、永遠に放置できるを優先して2~3分ほどdelay();
させるという暴挙で対策しました。
なので、delay();
については改善の余地しかないです。
そのままソースをコピペすると、交換相手が見つかっているのに全然交換を始めないというもどかしさを感じることになるでしょう。
後、羽集めの自動化が完成しました。
約1日放置して、下の数だけ集まりました。
これで思う存分努力値を触れるので、ほぼほぼ対戦以外のことはする必要がなくなりました。
羽集めに関しては、自分しかまだやっていない可能性すらある需要の塊だと思っているので、なるべく早めに紹介したいですね。
自動化ってスバラシイ!!!
では、良きプログラミングライフを!