0.ワット自動化ソースって何の話?
何かというと、これのことです。
本命にしようと思っていたブログがあり、本来、Qiitaの方でブログを書くつもりがなかったため、ソースだけこちらで書く形をとりました。
※本命はこっち
では、今回はなぜQiitaなの?というと、前回はソースを貼るならQiitaということを知らなかったからです。
前回で学んだので、今回は適したところで記事を書いている。それが理由です。
1.はじめに
今回ソースの解説といことで、まず初めに宣言しておくべきことがあります。
それは、解説のレベルについてです。
これで察しはつくと思いますが、例えば、大学生向けなのか小学生向けなのかとか。
使う教材や話すレベルが変わりますよね?
Qiitaはどうやら開発者向けのブログのようなので、余計、その「レベル」の話が必要不可欠なのではないかと思います。
では、今回の記事の「レベル」は?というと、「初心者向け」です。
ポケモン剣盾の自動化をやりたくて初めてプログラミングという世界に足を踏み入れようとしている。
そんな人間をターゲットに据えます。
そもそも自分が基礎程度の知識しかないからというのは内緒
※そんな希少種がここにたどり着くかは甚だ疑問ですが・・・w
2.ソースの解説 基本編
それでは、さっそく解説と行きましょう。
まずは初歩の初歩?から。
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}
↑のArduino IDEで「ファイル」→「新規ファイル」
を押すとスケッチに出てくるやつの説明をします。
void setup() {
// put your setup code here, to run once:
}
このvoid setup(){}
というものは、このプログラムが動き出したときに真っ先に読み込まれる処理になります。
簡潔に言うと、初期処理ですね。
void loop() {
// put your main code here, to run repeatedly:
}
こっちのvoid loop(){}
が大雑把に言うと、初期処理の後に実行される処理になります。
簡潔に言うと、本処理ですかね。
これじゃあ、初心者にはちんぷんかんぷんかもしれないので、例えを出しましょう。
例えば、今回のプログラムが体育の授業を行うためのプログラムだったとします。
で、体育の授業として真っ先に行うべきは、準備運動ですよね?
それを記載する部分が、void setup(){}
という部分となります。
実際に50m走など、体育の授業を記載する部分がvoid loop(){}
となります。
これが、Arduino IDEを使う上での基礎中の基礎となります。
※細かく見ると違う部分があるので、厳密に言うと違うことだけは頭の片隅に置いておいてください。
ただ、詳しく学んだ時に必ず違いが分かるはずですので、初心者の内はこの理解で問題ないです。
3.ソースの解説 中身編
とりあえず先頭から説明していきます。
#include <SwitchControlLibrary.h>
これはライブラリと言われるやつなんですが、初心者にも分かりやすく言うと、本来は「Arduino IDE」にないこの機能を拡張しますよって宣言になります。
ポケモンに例えれば、つい最近発表されたDLC(ダウンロードコンテンツ)のようなものです。
で、上の文章で何を機能追加しているのかというと、Switchのコントローラーの操作機能を追加しています。
使用する側の目線で言えば、Switchのコントローラーの操作するコード(プログラム)を使えるようにしています。
int MAXMONTH = 11; //月最大値(1月から11回更新で12月となるため)
int MAXDAY = 27; //日最大値(1日から27回更新で28日となるため)
// ※このプログラムでは、ひと月を27日とみなす(ひと月の最短日数が28日のため)
int month = MAXMONTH; // 月
int day = MAXDAY; // 日
んー。上から説明と言ったからには上からするつもりですが、これかぁ・・・。
これらは、変数と言われるもので、読んで字のごとく、プログラム上で値が変動する(値を変動させる)ものとなります。
変数の説明にはよく、箱に例えられるので、ひねらず、箱に例えようと思います。
int MAXMONTH
などの部分で、今後、このプログラム上でMAXMONTH、MAXDAY、month、dayという箱を使いますよ~と宣言してます。
で、後ろに= 11
などが記載されていますが、これは何かというと代入をしています。(ちなみに、代入は上書きです。)
MAXMONTHで言えば、11という数値をMAXMONTHというなの箱の中に入れています。
じゃあ、int month = MAXMONTH;
なんだ?というと、monthという箱の中に11という数値を入れています。
初めは慣れないかもしれませんが、プログラム上で変数名を書く場合は、原則、箱の中身でやり取りを行います。
あ、//月最大値(1月から11回更新で12月となるため)
はコメントです。//
以降に書かれた文字はプログラムとして認識されません。
今書かれているように、ほとんどの場合、説明書きに使われます。
// 初期処理 マイコン接続後1番に動きます
void setup(){
// LRボタン押下でコントローラーとして認識させる ←初期処理単体だとなぜかうまくいかない
SwitchControlLibrary().PressButtonL();
delay(500);
SwitchControlLibrary().PressButtonR();
delay(500);
SwitchControlLibrary().ReleaseButtonL();
delay(500);
SwitchControlLibrary().ReleaseButtonR();
delay(500);
}
ここは先程説明した、初期処理の部分です。
SwitchControlLibrary().PressButtonL();
は、初めに説明したライブラリによって使えるようになった拡張機能で、本来使えない機能となります。
で、これが何をやっているのかというと、SwitchのLボタンを押す。という動作をやっています。
※一応細かいことを説明しておくと、SwitchControlLibrary()
(ライブラリ)にある.PressButtonL()
とういプログラムを使えというコードになります。
SwitchControlLibrary().PressButtonR();
これが、Rボタンを押す。
SwitchControlLibrary().ReleaseButtonL();
これがLボタンを離す。
SwitchControlLibrary().ReleaseButtonR();
これがRボタンを離すです。
SwitchControlLibraryのButton系は英語を読めば察せるので、以降は登場しても説明を省きますね。
で、最後のdelay(500);
は0.5秒待つ。です。
delayという機能があり、()内に数値を入れることにより、数値の分だけプログラムが待ってくれます。
※()内の単位は1/1,000秒です。
なぜこのdelayが大量に登場するのかというと、Switchが認識しない速度で入力をしてしまうためですね。
上の処理で言うと、delayがなければ、LとRボタンを押しているはずなのに、先に押したLしか押したことになりません。
後、こっちのほうが重要なんですが、押したいタイミングで押せないと、自動化ができないからです。
ホームボタンを押して、ホームが表示されるまでの間に本体設定へカーソルを移動させる処理が終わっていたら、本体設定をいじれませんよね?
それじゃ困るので、いたるところで時間を調整しているわけです。
・・・で、次のvoid loop() なんですが、長いので抜粋しての説明とさせていただきます。
// 時間の変更
// 繰り返し処理 マイコンの接続をやめるまで{}内を繰り返します
void loop() {
// 年の処理 日を27回変更し、月を11回変更していたら
if (month == 0 & day == 0) {
MoveHatUp(); // 年を変更
delay(200);
MoveHatRight(); // 月の項目へ移動
delay(50);
MoveHatUp(); // 月を変更
delay(200);
month = MAXMONTH;// 月をリセット
MoveHatRight(); // 日の項目へ移動
delay(50);
DayReset(); // 日をリセット
delay(200);
}else{
// 月の処理 日を27回変更したいたら
if (day == 0) {
MoveHatRight(); // 月の項目へ移動
delay(50);
MoveHatUp(); // 月を変更
delay(200);
MoveHatRight(); // 日の項目へ移動
delay(50);
DayReset(); // 日をリセット
delay(200);
--month; // 月をカウントダウン
}else{
// 日の処理 日を27回変更していないなら
MoveHatRight(); // 月の項目へ移動
delay(50);
MoveHatRight(); // 日の項目へ移動
delay(50);
MoveHatUp(); // 日を変更
delay(200);
--day; // 日をカウントダウン
}
}
}
時間変更して穴にを調べるなど、画面通りの操作のため、ほぼ全て省略しましたが、SwitchControlLibrary().MoveHat();
の説明だけはします。
これは十字キーの操作についてのコードで、MoveHat()
の()内に0~8の数値を入れて方向を決めます。
方向は、以下の通りです。
数値 | 方向 |
---|---|
0 | 上 |
1 | 右上 |
2 | 右 |
3 | 右下 |
4 | 下 |
5 | 左下 |
6 | 左 |
7 | 左上 |
8 | 中央 |
で、残っているここが一番プログラムらしい処理をしているところ。
if (month == 0 & day == 0) {
これがif文と言われるもので、プログラムをやるならこれは必須です。
内容については、英語の通りで、もし()内であれば○○で、そうでないなら○○です。というものとなります。
個人的には分岐処理と言われるとすっごい分かりやすいので、分岐処理ですと言っておきます。
プログラム的な説明をすると、()内が条件。もし何々であったらの"何々"の部分。
初めの{}内が真の場合、else{}内が偽の場合です。
(例)もし、リンゴが赤ければ収穫をします。リンゴが赤くないなら収穫しません。
if(リンゴ == 赤い){
収穫する。
}else{
収穫しない。
}
※ちなみに、==は等しいという記号。=が代入式の記号となっているため、区別すべくこうなっている。
ここのif文で何をやっているのかについては、まず、この時間の変更のブロック全体で何をやっているかを説明しなきゃうまく説明できません。
そのため、まずは全体について説明します。
Q.このブロック全体を通して何をやっているのか?
A.日付変更です。
とまぁ、簡単に説明できるんですが、日付を変更するためには実は考慮しなくてはいけない部分が多数あるんです。
それはどこといいますと、本来は親切な機能であるはずの「日付や月を繰り上げない仕組み」になります。
ピンとこないでしょう。自分も事前知識なしでこんなこと言われればハテナですよ。
何かというと、日付をいじるときに、31日(末日)からもう一つ上へ行った時の処理のことです。
例として1月31日を上げましょう。
1月31日からもう一つ上へ行くと、2月1日とはならず、1月1日になるんです。
これのことを繰り上がらないと表現しました。
こうなると、単純に1日づつ加算していくと、1月31日→1月1日という変更を迎えることとなり、自動ワット稼ぎの条件である「過去日に変更してはいけない」に抵触するわけです。
※抵触しても回収できないだけで問題はないわけですが、自動化的には穴からワットを回収しました等のメッセージを確認するために押しているAボタンで、「みんなで挑戦」→「ポケモンをかえる」を選択することになり、ボックスが表示されその後の身動きが取れなくなり、自動処理がそこで実質的なストップとなります。
それを回避するためにはプログラムで日数を判定し、年と月を変えなくてはいけません。
しかし、カレンダーを見てもらえれば分かりますが、ひと月28日間~31日間と、月ごとにぶれがありますよね?何ならうるう年まで考えなきゃいけません。
これが難しい。
※ほかの自動化プログラムは年を増やすことでこのわずらわしさから逃れているようです。
※上限が何年なのか知らないのでわかりませんが、それってワットカンストまで放置できるんですかね?
そこで考えたのが、「ブレがあるならブレ幅の最小値を最大値にすればいいじゃないか。」ということでした。
つまり、28日経てば月を加算させるという方式をとったというわけですね。
※こうすることで、ネックであった2月28日でも28日で翌月へ行くため、2月1日に戻ってワットの回収に失敗することがなくなります。
ということで、方向性は説明したのでプログラムの説明をします。
// 年の処理 日を27回変更し、月を11回変更していたら
if (month == 0 & day == 0) {
MoveHatUp(); // 年を変更
delay(200);
MoveHatRight(); // 月の項目へ移動
delay(50);
MoveHatUp(); // 月を変更
delay(200);
month = MAXMONTH;// 月をリセット
MoveHatRight(); // 日の項目へ移動
delay(50);
DayReset(); // 日をリセット
delay(200);
}else{
if (month == 0 & day == 0) {
↑は年を加算するかどうかの確認です。
月も日も減算式で数えているため、月と日が0になる時とは、12月28日ということになるため、monthとdayが0の場合は年を加算させます。
※加算式で数えていれば、month == 11 & day == 27
となっています。'(12月-1月=11か月、28日-1日=27日)
MoveHatUp(); // 月を変更
ここで上を入力しています。12月の上は1月なので翌年の1月にしています。
※MoveHatUp();
については、後で説明します。
※今は上を入力と解釈してください。
month = MAXMONTH;// 月をリセット
翌年となったため、monthは0から11へ中身を元に戻します。これにより、再び減算式でカウントすることができます。
DayReset(); // 日をリセット
ここで、27日分下を入力し、翌年の1日にしています。
月と違って、上の入力で1に戻そうとすると、月ごとに日数が違うため、うまくいきません。
※DayReset();
については、後で説明します。
※今は日にちを1日へ戻すと解釈してください。
で、当然、12月28日でないなら、年を加算してはいけないため、else{}の部分の処理をすることとなります。
}else{
// 月の処理 日を27回変更したいたら
if (day == 0) {
MoveHatRight(); // 月の項目へ移動
delay(50);
MoveHatUp(); // 月を変更
delay(200);
MoveHatRight(); // 日の項目へ移動
delay(50);
DayReset(); // 日をリセット
delay(200);
--month; // 月をカウントダウン
}else{
※余談:else{}の中にif文を書くなら、基本的にelse if{}と書かれる。(コメントを書きたくてこの形にした)
※else if (day == 0) {}else{}の形。
※ただし、初心者目線だと違いはない。
こちらも年の処理と同じで、月を加算すべきかどうかを判定し、加算すべき場合に加算処理を行っている。
年の処理にない点としては、減算処理を行っているところ。
--month;
の"--"はmonthを-1するという内容。本来はmonth = month - 1;
という代入式を使う。
(monthという変数から1を引いた結果をmonthという変数に代入しろ。という意味。)
1づつ加算することや1づつ減算することが多いため、このような表現があります。
ちなみに加算の場合は、++month;
となる。(←こっちのほうが使う)
}else{
// 日の処理 日を27回変更していないなら
MoveHatRight(); // 月の項目へ移動
delay(50);
MoveHatRight(); // 日の項目へ移動
delay(50);
MoveHatUp(); // 日を変更
delay(200);
--day; // 日をカウントダウン
}
dayは大差ありません。monthがdayになっただけのようなもの。
//Aボタンを押下して離すまで
void ButtonA(){
SwitchControlLibrary().PressButtonA();
delay(50);
SwitchControlLibrary().ReleaseButtonA();
}
//ホームボタンを押下して離すまで
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
}
//日をリセット
void DayReset(){
for (day ; day < MAXDAY ; day++){
MoveHatDown();
delay(50);
}
}
最後、これらは関数と呼ばれるものです。
私が書くプログラムは初心者に毛が生えた程度のものなので素直な見た目ですが、上級者になればなるほど、初心者目線では難解なプログラムとなります。
※あくまで初心者目線では。です。少し慣れれば、むしろ見やすいまであります。
なぜ難解になるかというと、この関数が中心となるからです。
関数というのは、一連の処理を登録しておくようなものです。
学校の授業初めの挨拶で例えてみましょう。
先生がやってきて、「はい、じゃあ挨拶!」と言ったとしましょう。
そしたら、日直だか学級委員だか、決められら人が、号令をかけ始めるなどの一連の動作を行い、挨拶すると思います。
関数はこの例でいうところの"挨拶"に当たって、プログラム上で"挨拶"と書くと"挨拶"として登録されている処理が実行されるわけです。
(例えで言うところの、日直→号令→挨拶の処理が登録されており、挨拶書くとその処理が実行される。)
そして、世の中というのは、突き詰めれば数多ある定型的なことをやっているだけなので、上級者になればなるほど、関数としてその定型的なことを登録して、関数でプログラムを作るわけなんです。
後、規模のでかいプログラムだと、同じ処理いたるところに書くことになるので、文字数が減ります。
では、今回のプログラムではどんなことを登録しているのかを見ていきましょう。
//Aボタンを押下して離すまで
void ButtonA(){
SwitchControlLibrary().PressButtonA();
delay(50);
SwitchControlLibrary().ReleaseButtonA();
}
これは、ライブラリのAボタンを押す → 0.05秒待つ → Aボタンを離す という処理が登録されています。
ここまで説明すれば察しがつくと思いますが、我々が日常的に言っている方の「Aボタンを押す」という動作を登録しています。
プログラム的な「Aボタンを押す」は、我々が言うところの「Aボタンを長押しする」に当てはまるかと思います。
なので、視覚的に分りやすくなるよう、また、3行必要とするプログラムを1行で表現できるようにこのような登録をしました。
使い方は実際のソースを見てもらえば分かりますが、ButtonA();
となります。
//下を入力して離すまで
void MoveHatDown(){
SwitchControlLibrary().MoveHat(4); // down
delay(50);
SwitchControlLibrary().MoveHat(8); // center
}
こちらも、方向キーに変わっただけで、やっていることは同じです。
下を長押しするんじゃなくて、一度押したら話します(中央にする)よ。って関数です。
//日をリセット
void DayReset(){
for (day ; day < MAXDAY ; day++){
MoveHatDown();
delay(50);
}
}
これだけはかなり特殊ですね。
上でも説明しましたが、やっていることは28日から1日に日数を戻す処理です。
詳細を説明すると、for (day ; day < MAXDAY ; day++){
これがループ文という構文です。
条件に当てはまっている間、{}内の処理を繰り返すというもので、if文に続いて、プログラミングには必須な構文となっています。
具体的にはfor( 初期値 ; 条件 ; ループごとに加算or減算 ){
となっております。
今回のfor文で説明すると、初期値はdayという変数の中身(dayは0です)。
※今回は減算を繰り返し、0になった時のみ日をリセットするため、中身は0。
条件は、dayという変数の中身が27(MAXDAY)未満であること。
※MAXDAYはプログラムの一番上で27を代入している。
加算or減算はループごとに1づつ加算となります。
※day+2とかもできたはず。滅多に使わないので自信はない。
つまり、0から{}内の処理をループし、1回処理するたびにdayを1つづ加算する。
そして、dayが27以上になった時、ループをやめる。という意味になります。
※27未満の間、ループを続けるという意味でもある。
なので、0,1,2,3,~,26(27回目はループ抜けるため行われない)と{}内の処理が行われるため、MoveHatDown();
が、計27回行われるということになります。
28日から27日分を引きますと1日となりますので、しっかり、日をリセットできている。ということになりますね。
4.あとがき
はい、ということでこれで全説明を終えました。いや、終えたつもりです。
初心者をターゲットになるべくわかりやすく説明をしたつもりですが、色々と省略しちゃいましたし、説明が行き届いていない気配がプンプンします。
もしよければですが、「ここが分からない」や「ここが分かりやすかった」など、感想をいただけると嬉しいです。
後、ワット自動回収につきましては、まだ不完全な部分があるようで、約1日ほど放置してみたら7年分ほどのワット回収した後に失敗していました。
どこかの操作で発生するラグによって、必要な入力が消えたっぽいです。
Arduino UNOのほうがソースが転がっていて、みんなもそっちで頑張っている気配があるので、おすすめですが、私の様に道を外してしまって私のソースを使っている人はご注意ください。
↓約7年分のワット
最後に私が現在作成できている自動化は、ワット回収、A連打(穴掘り兄弟で使用)、ボール10個買い(自動レイド用のボール購入時に作成。プレミアムボール稼ぎにも有用)、自動レイドとなります。
日付変更に比べればかなり楽なものだったので紹介するかは微妙ですが、気になる方はコメントでもしてください。
@pokezaresu ←ツイッターでも可。
それでは、よきプログラミングライフを!