#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
■マジカル交換の自動化
https://qiita.com/pokezaresu/items/b86970a716c64ef46370
#1.はじめに
いきなりですが、私は今までのポケモンにかなりの不満を持っていました。
それが何かというと、**「時間がかかりすぎる」**ことです。
羽集めに興味を持っている人であれば、それがどういうことか十分に理解してくれると思っています。
ゲームフリークさんは徐々にですが、この**「時間がかかりすぎる」という事態を問題視し、改善を図っているように思います。
しかし、自分とは考えが違って「やり込み要素」**という部分も重要視しており、毎回(自分にとって)余計な事や、まるで改善しようとしない要素が多々ありました。
それが今回の技レコード追加であったり、努力値振りであったりします。
※4世代からwi-fiで対戦していた身からすれば、あり得ないほど改善しましたがね・・・。
当時は、親個体から最大3vまでしか遺伝しない。かわらずの石の性格遺伝が50%。伝説等の固定シンボル3Vの保証なし。と散々でした。
不満が多すぎて脱線してしまいましたが、マイコンの自動化によりそれらは一新され、今回の羽集めによりほぼ理想のポケモンとなります。
皆さんもぜひ、快適なポケモンライフをお送りください。
↓の様に動きます。また、自分はこれで満足したので変える予定はないですが、羽の落ちている位置が固定であることが分かったので、改善の余地がかなりあります。
羽集めを自動化しました pic.twitter.com/YiJaIvvaEo
— ウマグざれ (@pokezaresu) February 4, 2020
#2.ソース
#include <SwitchControlLibrary.h>
// SwitchControlLibrary().MoveLeftStick(255,128); // 右
// SwitchControlLibrary().MoveLeftStick(0,128); // 左
// SwitchControlLibrary().MoveLeftStick(128,0); // 上
// SwitchControlLibrary().MoveLeftStick(128,255); // 下
int timecnt = 0;
int cnt = 0;
int LRflg = 1; // 0:左 1:右
// 初期処理 マイコン接続後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(2000);
MoveBridge();
SwitchControlLibrary().MoveLeftStick(255,128); // 右
delay(50);
LRflg = 1;
}
// 繰り返し処理 マイコンの接続をやめるまで{}内を繰り返します
void loop() {
// 開始
ButtonA();
delay(50);
timecnt++;
if (timecnt > 140){
timecnt = 0;
cnt++;
SwitchControlLibrary().MoveLeftStick(128,0); // 上
delay(400);
if(cnt > 2){
cnt = 0;
LRflg = 0;
SwitchControlLibrary().MoveLeftStick(128,128); // 中央
delay(50);
DateChange();
Reload(); // ポケセンへタクシー
MoveBridge(); // 橋まで移動
}
if(LRflg == 1){
LRflg = 0;
SwitchControlLibrary().MoveLeftStick(0,128); // 左
delay(50);
}else{
LRflg = 1;
SwitchControlLibrary().MoveLeftStick(255,128); // 右
delay(50);
}
}
}
//Aボタンを押下して離すまで
void ButtonA(){
SwitchControlLibrary().PressButtonA();
delay(50);
SwitchControlLibrary().ReleaseButtonA();
}
//Bボタンを押下して離すまで
void ButtonB(){
SwitchControlLibrary().PressButtonB();
delay(50);
SwitchControlLibrary().ReleaseButtonB();
}
//Xボタンを押下して離すまで
void ButtonX(){
SwitchControlLibrary().PressButtonX();
delay(50);
SwitchControlLibrary().ReleaseButtonX();
}
//ホームボタンを押下して離すまで
void ButtonHome(){
SwitchControlLibrary().PressButtonHome();
delay(50);
SwitchControlLibrary().ReleaseButtonHome();
}
//+ボタンを押下して離すまで
void ButtonPlus(){
SwitchControlLibrary().PressButtonPlus();
delay(50);
SwitchControlLibrary().ReleaseButtonPlus();
}
//下を入力して離すまで
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 MoveHatLeft(){
SwitchControlLibrary().MoveHat(6); // left
delay(50);
SwitchControlLibrary().MoveHat(8); // center
}
//上を入力して離すまで
void MoveHatUp(){
SwitchControlLibrary().MoveHat(0); // up
delay(50);
SwitchControlLibrary().MoveHat(8); // center
}
int MAXMONTH = 11; //月最大値(1月から11回更新で12月となるため)
int MAXDAY = 27; //日最大値(1日から27回更新で28日となるため)
// ※このプログラムでは、ひと月を27日とみなす(ひと月の最短日数が28日のため)
int month = MAXMONTH; // 月
int day = MAXDAY; // 日
//日付変更処理
void DateChange(){
// 日付変更
ButtonHome();
delay(1000);
MoveHatDown();
delay(100);
MoveHatRight();
delay(100);
MoveHatRight();
delay(100);
MoveHatRight();
delay(100);
MoveHatRight();
delay(100);
ButtonA();
delay(1500);
// 設定画面
SwitchControlLibrary().MoveHat(4); // down
delay(2000);
SwitchControlLibrary().MoveHat(8); // center
delay(50);
MoveHatRight();
delay(50);
MoveHatDown();
delay(50);
MoveHatDown();
delay(50);
MoveHatDown();
delay(50);
MoveHatDown();
delay(50);
ButtonA();
delay(500);
// 時間設定
MoveHatDown();
delay(50);
MoveHatDown();
delay(50);
ButtonA();
delay(500);
// 時間の変更
// 年の処理 日を28回変更し、月を12回変更していたら
if (month == 0 & day == 0) {
MoveHatUp(); // 年を変更
delay(200);
MoveHatRight(); // 月の項目へ移動
delay(50);
MoveHatUp(); // 月を変更
delay(200);
month = MAXMONTH; // 月をリセット
MoveHatRight(); // 日の項目へ移動
delay(50);
DayReset(); // 日をリセット
delay(200);
}else{
// 月の処理 日を28回変更したいたら
if (day == 0) {
MoveHatRight(); // 月の項目へ移動
delay(50);
MoveHatUp(); // 月を変更
delay(200);
MoveHatRight(); // 日の項目へ移動
delay(50);
DayReset(); // 日をリセット
delay(200);
--month; // 月をカウントダウン
}else{
// 日の処理 日を28回変更していないなら
MoveHatRight(); // 月の項目へ移動
delay(50);
MoveHatRight(); // 日の項目へ移動
delay(50);
MoveHatUp(); // 日を変更
delay(200);
--day; // 日をカウントダウン
}
}
ButtonA();
delay(50);
ButtonA();
delay(50);
ButtonA();
delay(50);
ButtonA();
delay(100);
ButtonHome();
delay(1000);
ButtonA();
delay(1000);
}
//日をリセット
void DayReset(){
for (day ; day < MAXDAY ; day++){
MoveHatDown();
delay(50);
}
}
//Mapを再読み込み
void Reload(){
ButtonX();
delay(900);
ButtonA();
delay(2000);
SwitchControlLibrary().MoveHat(6); // left
delay(300);
SwitchControlLibrary().MoveHat(8); // center
delay(500);
ButtonA();
delay(600);
ButtonA();
delay(4300);
}
//橋まで移動
void MoveBridge(){
ButtonPlus();
delay(500);
SwitchControlLibrary().MoveLeftStick(128,255); // 下
delay(485);
SwitchControlLibrary().MoveLeftStick(255,128); // 右
delay(13600);
SwitchControlLibrary().MoveLeftStick(128,128); // 中央
delay(500);
ButtonPlus();
delay(1000);
}
#3.使い方
・エンジンシティの左のポケモンセンターへ「空飛ぶタクシー」をして着地した位置が開始地点
※ポケセンへ入ってから出た位置でも可
・メニューを開いた時に空飛ぶタクシーが選択される状態にしておく
↑これは非常に重要です。
・自転車には乗らない(勝手に乗ります)
・今回はソースをいじる必要はありません
↑改善したいって人はいじってください
#4.解説
いつものごとく要点以外の説明は省きます。
今回は今までの集大成かのようなソースとなっていて、関数の使い方がちゃんとしたプログラムっぽくなっています。
※ぽいだけでちゃんとはしていないけど
// 初期処理 マイコン接続後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(2000);
MoveBridge();
SwitchControlLibrary().MoveLeftStick(255,128); // 右
delay(50);
LRflg = 1;
}
初期処理にちょっとだけ処理があります。
まずは、MoveBridge()
関数について。
//橋まで移動
void MoveBridge(){
ButtonPlus();
delay(500);
SwitchControlLibrary().MoveLeftStick(128,255); // 下
delay(485);
SwitchControlLibrary().MoveLeftStick(255,128); // 右
delay(13600);
SwitchControlLibrary().MoveLeftStick(128,128); // 中央
delay(500);
ButtonPlus();
delay(1000);
}
関数名とコメントの説明の通り、羽の落ちている橋まで移動を行う関数です。
この関数を呼び出すだけでポケモンセンターから橋までの移動が行われます。
処理的には、自転車に乗ってからポケモンセンターの少し下に移動し、ひたすら右へ。
橋に到着したら自転車を降りるという処理となっています。
delay();
の指定時間を調べるのが面倒なだけで、特に難しいことはしていませんね。
自転車からわざわざ降りているのには理由があって、当初は調査不足で羽の落ちている場所がランダムだと思っていたからです。
移動しながらA連打をすることで羽を回収していく予定だったので、自転車に乗っていると拾い漏れが発生すると考え、徒歩で拾いまわることを考えました。
橋まで移動した後は、羽を拾うための右移動を行っています。
SwitchControlLibrary().MoveLeftStick(255,128); // 右
delay(50);
LRflg = 1;
LRflg = 1;
については、後で説明しますが、橋上での左右の移動をこの変数が0か1かで管理しているため、上で右移動のコードを書いたため、変数に1を代入しています。
// 繰り返し処理 マイコンの接続をやめるまで{}内を繰り返します
void loop() {
// 開始
ButtonA();
delay(50);
timecnt++;
if (timecnt > 140){
timecnt = 0;
cnt++;
SwitchControlLibrary().MoveLeftStick(128,0); // 上
delay(400);
if(cnt > 2){
cnt = 0;
LRflg = 0;
SwitchControlLibrary().MoveLeftStick(128,128); // 中央
delay(50);
DateChange();
Reload(); // ポケセンへタクシー
MoveBridge(); // 橋まで移動
}
if(LRflg == 1){
LRflg = 0;
SwitchControlLibrary().MoveLeftStick(0,128); // 左
delay(50);
}else{
LRflg = 1;
SwitchControlLibrary().MoveLeftStick(255,128); // 右
delay(50);
}
}
}
ここがマイコン接続後にループし続ける処理の部分となります。
いわゆるメイン処理と呼ばれるものですが、マイコンだとloop()
という名前のため、ループ処理って言いたくなりますね。
今回のメイン処理は、処理のほとんどを関数に記載して関数の呼び出しを中心としたため、行数が少ないですね。
void loop() {
// 開始
ButtonA();
delay(50);
timecnt++;
if (timecnt > 140){
// 説明のため省略
}
まず、この部分で何をやっているのかというと、(移動しながら)A連打しています。
初期処理を行った時点で橋へ移動し、右へ移動を始めているため、A連打だけで羽回収が行われます。
if (timecnt > 140){}
このif文は何かというと、delay(50);
を140回繰り返したかを判定しています。
本当はちゃんと時間を図る手段があって、それを使うべきなんだと思いますが、調べるのが面倒だったので、delay()
で時間を図っています。
50*140(0.001秒単位)だけ歩くといい感じに羽を回収できたというわけです。
timecnt
という自分特有の関数名でも何となく察せますかね?
では、省略したif文の中身について。
void loop() {
if (timecnt > 140){
timecnt = 0;
cnt++;
SwitchControlLibrary().MoveLeftStick(128,0); // 上
delay(400);
if(cnt > 2){
cnt = 0;
LRflg = 0;
SwitchControlLibrary().MoveLeftStick(128,128); // 中央
delay(50);
DateChange();
Reload(); // ポケセンへタクシー
MoveBridge(); // 橋まで移動
}
if(LRflg == 1){
LRflg = 0;
SwitchControlLibrary().MoveLeftStick(0,128); // 左
delay(50);
}else{
LRflg = 1;
SwitchControlLibrary().MoveLeftStick(255,128); // 右
delay(50);
}
}
簡単に言うと、50*140(0.001秒単位)は橋の片道分なので、往復になるよう、さっきとは逆方向に転換します。
それが↓の部分です。
SwitchControlLibrary().MoveLeftStick(128,0); // 上
delay(400);
if(cnt > 2){
// ここは別の処理のため省略
}
if(LRflg == 1){
LRflg = 0;
SwitchControlLibrary().MoveLeftStick(0,128); // 左
delay(50);
}else{
LRflg = 1;
SwitchControlLibrary().MoveLeftStick(255,128); // 右
delay(50);
}
橋の一番下から回収を始めているため、少しだけ上へ移動して回収する位置を変更します。
そして、方向転換をします。
LRflg
が1なら、右へ移動していたはずなので、左へ。
LRflg
が0なら、左へ移動していたはずなので、右へ。
という感じです。
逆方向へ移動を始めたので、LRflg
に逆方向の数字を代入することも忘れずにやっています。
これを繰り返せば、橋の上の羽を回収しきることが可能です。
※ちなみに片道を3回移動すれば回収しきれます
回収しきった時に何をする必要があるのかというと、日付の変更です。
そして、ただ日付を変更するだけでは羽が落ちていません。
マップを再読み込みさせる必要があります。
その再読み込みに使う手段が空飛ぶタクシーとなります。
それらの処理は、if(cnt > 2){}
の部分で行っています。
if(cnt > 2){
cnt = 0;
LRflg = 0;
SwitchControlLibrary().MoveLeftStick(128,128); // 中央
delay(50);
DateChange();
Reload(); // ポケセンへタクシー
MoveBridge(); // 橋まで移動
}
cntは片道を移動した回数で、3回目にif文の中の処理を実行します。
※0、1、2で片道3回分の移動をしています。なので、正確に言うと移動した回数ではないですね・・・。
橋の移動(羽の回収)は終わったので、カウント(cnt)を元に戻します。
if(cnt > 2){}
の処理が終わってLRflg
の判定処理を行ったときに右移動が選択されるようにLRflg
に0を代入しておきます。
現時点では方向キーを押しっぱなしの状態なので、離(中央に戻)します。
そして、DateChange()
関数で日付を変更し、Reload()
関数でエンジンシティの左のポケモンセンターに移動します。
最後に橋まで移動すれば、初期処理を終えてloop()
を始めた状態と同じ状態に戻るため、今までと同様に羽の回収が始まります。
DateChange()
関数は今まで出てきた日付変更を関数として外だししただけです。特に変更点はありません。
Reload()
は空飛ぶタクシーでポケセンへ移動するだけです。
//Mapを再読み込み
void Reload(){
ButtonX();
delay(900);
ButtonA();
delay(2000);
SwitchControlLibrary().MoveHat(6); // left
delay(300);
SwitchControlLibrary().MoveHat(8); // center
delay(500);
ButtonA();
delay(600);
ButtonA();
delay(4300);
}
Xボタンを押してメニューを開く。
既にカーソルが合わせてあるはずの空飛ぶタクシーをAボタンで選択。
マップが開かれ、橋の上にあるカーソルをエンジンシティの左のポケモンセンターへ移動。
Aボタンで選択して、Aボタンで「はい」を選択。
空を飛んで操作ができるようになるまでdelay()
。
これでエンジンシティの左のポケモンセンターに着地するので、初期処理と同様にMoveBridge()
関数で橋まで移動します。
#5.さいごに
努力値を指定して振るという希望がようやく叶いました。
据え置きかつ携帯できるゲームというコンセプトでSwitch設計し、携帯ゲームとして定着させていたポケモンにUSBポートを与えてくださった任天堂には感謝するばかりです。
なんならポケモン対戦するより自動化で環境を充実させる作業のほうが楽しいまでありました。
制作側は全く意図していないグレーな部分で楽しんでいるのは罪なのかもしれませんが、個人的にはもっと早くあるべき形だったと思っています。
現に、Amazonのマイコンの売れ行きは異常で、マクロ付きコントローラーもかなり売れている認識です。
それほどまでに皆が欲していたんじゃないかと思います。
ただし、やはり自覚しておかなければならないのは、任天堂やゲーフリがこの事態を十中八九良しとはしないことです。
あくまでも**グレーである。**といことは忘れないようにしましょうね。
それで、今後の方針はというと、ランクマッチの上位を目指したり、動画を撮影、編集、投稿したりしたいなぁって考えています。
育成に不便しなくなったというのに、対戦しなかったら意味ないですよね。
ポケモンの考察とか、PT構築の記事とかも書いてみたいなぁって思っているので、書けるほどの実力を備えたいですね。
それと、手持ちの自動化ソースも様子を見てポンッと貼っておこうかなと思っています。
あ、後、乱数の勉強始めました。
うまくいくかは不明ですが、絶賛消費中です。
恐らくですが、光ってくれるんじゃないかなぁ・・・。
では、今回はここまでにします。
よきプログラミングライフを!