【マイコン】ポケモンSVのボックスに眠っているタマゴを孵化していくコード!【改良版】

ポケモンSVの孵化自動化コードは以前にも作ったのですが、改良版ができたため公開します。

関連記事

ポケモンSVにおける当ブログでは初の自動化コードです! 今回はボックスのタマゴをひたすらに孵化していくコードを作りました。(タマゴ受け取り部分は一旦おあずけです) ポケモンSVではマイコンでの自動化がなかなか難しいのですが、ある[…]

ポケモンSV ボックスのタマゴ 自動孵化!

この記事をご覧になる前に

マイコンをまだ導入していない方は、以下の記事を参考に導入してみてください。このブログでは、Arduino Leonardoというマイコンを使っているため、異なる種類のものではうまく動かない可能性があります。

関連記事

マイコンと呼ばれるものをご存知でしょうか? Switchに接続するだけで様々な作業を "自動" で行ってくれるというものです。 導入すれば作業が楽になるだけでなく、寝てる間に色々稼ぐこともできちゃう便利なアイテムなわけですね! […]

マイコンを使って
ポケモン剣盾
自動化しよう!
【導入編】

今回のコードは「NintendoSwitchControlLibrary」を使用しています。まだダウンロードされていない方は、解凍したフォルダを『Arduino』フォルダの中にある『libraries』フォルダの中にコピーしておいてください。

おそらくですが、今回のコードはNintendoSwitchControlLibraryのバージョンが1.3以降でないと動かないと思われます。
2022年8月末に1.3をリリースしていますので、それ以前に導入されている方はライブラリの更新をお願いいたします。

コード利用における注意点!

まず最初に大事なことを書いておくと、今回のコードは完全な放置はできません!!!

ポケモンSVは処理落ちがあったり、それによってコマンドに漏れが生じることがあります。

また、今回のコードに限らずなのですが、エラー落ちが起きる可能性もあります。ポケモンSVは特にエラーが発生しやすいという話を聞いているので、そういった観点でもある程度見張れる状態での使用をおすすめいたします。

エラー落ちした場合、ホーム画面でどんな操作をするかわかりません。e-shopで勝手にソフトを購入したり、ポケモンのセーブデータの削除、Switchの初期化などを行ってしまう可能性も0ではないです。放置は自己責任でお願いいたします。

ソースコード

実際に書いたコードはこちらになります。

旧コードはこちら
/**
 * ボックスに眠っているタマゴを順に孵化していくスケッチ
 *
 * ※このスケッチはタマゴの孵化のみでタマゴ受け取りは行いません。タマゴは事前に用意しておいてください。
 *
 * 初期条件は以下の通り
 * 1.「セルクルタウン・西」から北に向かった場所にある、オリーブころがし場の柵の中にいること(余裕があればオリーブを端のほうに動かしておくとなお良いです)
 * 2.ポケモンが連れ歩き状態でないこと
 * 3.ライド状態でないこと
 * 4.手持ちが「ほのおのからだ」の特性持ちのポケモン1匹のみであること
 * 5.孵化させたいタマゴの預けてあるボックスが最初に開くこと(複数ボックス孵化させる場合は、ボックスが連続するような配置にしておいてください)
 * 6.孵化させるボックスにタマゴを敷き詰めてあること(空きがあると不安定になるかも)
 * 7.オフライン状態であること
 * 8.無線のコントローラーが接続されていないこと
 * 9.「設定」から「話の速さ」を「はやい」に、「ニックネーム登録」を「しない」にしておくこと
 */

////////////////// libraries ////////////////////////////////

// ライブラリを読み込むためのコード
#include <NintendoSwitchControlLibrary.h>

////////////////// options ////////////////////////////////

// 何ボックス分ループさせるか?
const int BOX_COUNT = 5;

// 1ボックスの孵化を終えるごとにレポートを書くかどうか(書く: 1, 書かない: 0)
const int REPORT_OPTION = 0;

////////////////// constant parameters ////////////////////////////////

// Custom版のpushButton・pushHatにおける押下時間
const int CUSTOM_INPUT_TIME = 200;

// ボックスの列数
const int BOX_COLUMN_LENGTH = 6;

// ボタン押下時の標準的な待機時間
const int BASE_PUSH_BUTTON_TIME_MS = 1000;

// カーソル操作時の標準的な待機時間
const int BASE_CURSOR_OPERATION_TIME_MS = 200;
const int HALF_CURSOR_OPERATION_TIME_MS = 100;

// メニューの開閉にかかる待機時間
const int MENU_OPEN_OR_CLOSE_TIME_MS = 1500;

////////////////// variable parameters ////////////////////////////////

// ループをカウントするための変数
int loopCount = 0;

////////////////// methods ////////////////////////////////

/**
 * pushButtonのカスタム版(ボックス操作時にコマンド漏れが起きることを抑制するために用意)
 *
 * @param uint16_t      button     押すボタン
 * @param unsigned long delay_time ボタンを押した後の待ち時間(1秒 = 1000)
 * @param unsigned int  loop       ボタンを押す回数(省略可、デフォルトは1)
 */
void pushButtonCustom(uint16_t button, unsigned long delay_time, unsigned int loop = 1) {
    for (unsigned int i = 0; i < loop; i++) {
        SwitchControlLibrary().pressButton(button);
        SwitchControlLibrary().sendReport();
        delay(CUSTOM_INPUT_TIME);
        SwitchControlLibrary().releaseButton(button);
        SwitchControlLibrary().sendReport();
        delay(delay_time);
    }
    delay(CUSTOM_INPUT_TIME);
}

/**
 * pushHatのカスタム版(ボックス操作時にコマンド漏れが起きることを抑制するために用意)
 *
 * @param uint8_t       hat        押す十字キーのボタン
 * @param unsigned long delay_time ボタンを押した後の待ち時間(1秒 = 1000)
 * @param unsigned int  loop       ボタンを押す回数(省略可、デフォルトは1)
 * @return void
 */
void pushHatCustom(uint8_t hat, unsigned long delay_time, unsigned int loop = 1) {
    for (unsigned int i = 0; i < loop; i++) {
        SwitchControlLibrary().pressHatButton(hat);
        SwitchControlLibrary().sendReport();
        delay(CUSTOM_INPUT_TIME);
        SwitchControlLibrary().releaseHatButton();
        SwitchControlLibrary().sendReport();
        delay(delay_time);
    }
    delay(CUSTOM_INPUT_TIME);
}

/**
 * Switchをスリープさせる
 *
 * @return void
 */
void sleepGame() {
    holdButton(Button::HOME, 1500);
    pushButton(Button::A, BASE_PUSH_BUTTON_TIME_MS);
}

/**
 * レポートを書く
 *
 * @return void
 */
void writeReport() {
    pushButton(Button::X, MENU_OPEN_OR_CLOSE_TIME_MS);
    pushButton(Button::R, 1500);
    pushButton(Button::A, 4000);
    pushButton(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    pushButton(Button::B, MENU_OPEN_OR_CLOSE_TIME_MS);
}

/**
 * ボックスを開く
 *
 * @param bool is_init
 * @return void
 */
void openBox(bool is_init = false) {
    pushButton(Button::X, MENU_OPEN_OR_CLOSE_TIME_MS);

    // 初回のみカーソルを合わせる
    if (is_init) {
        holdHat(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
        holdHat(Hat::UP, 2000);
        pushHat(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
    }

    pushButton(Button::A, 4000);
}

/**
 * ボックスを閉じる
 *
 * @return void
 */
void closeBox() {
    pushButton(Button::B, 3000);
    pushButton(Button::B, MENU_OPEN_OR_CLOSE_TIME_MS);
}

/**
 * ポケモンを預ける
 *
 * @param int box_line
 * @return void
 */
void putInPokemons(int box_line) {
    // 手持ちのポケモンを範囲選択する
    pushHatCustom(Hat::LEFT, HALF_CURSOR_OPERATION_TIME_MS);
    pushHatCustom(Hat::DOWN, HALF_CURSOR_OPERATION_TIME_MS);
    pushButtonCustom(Button::MINUS, BASE_PUSH_BUTTON_TIME_MS);
    holdHat(Hat::DOWN, 1000);
    pushButtonCustom(Button::A, BASE_CURSOR_OPERATION_TIME_MS);

    // 範囲選択したポケモンをボックスに預ける
    pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS + 100, box_line);
    pushHatCustom(Hat::UP, BASE_CURSOR_OPERATION_TIME_MS);
    pushButtonCustom(Button::A, BASE_CURSOR_OPERATION_TIME_MS);

    // ボックスがいっぱいになったら、次のボックスに移動させる
    if (box_line == BOX_COLUMN_LENGTH) {
        pushButtonCustom(Button::R, BASE_CURSOR_OPERATION_TIME_MS);

        // 最後のボックスである場合、その後のカーソル操作は行わない
        if (loopCount == BOX_COUNT - 1) return;

        pushHatCustom(Hat::RIGHT, HALF_CURSOR_OPERATION_TIME_MS);
    }

    // 次の処理に繋げやすくするため、カーソルを移動させる
    pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
}

/**
 * タマゴを引き出す
 *
 * @param int box_line
 * @return void
 */
void pullOutEggs(int box_line) {
    pushButtonCustom(Button::MINUS, BASE_PUSH_BUTTON_TIME_MS);
    holdHat(Hat::DOWN, 1000);
    pushButtonCustom(Button::A, BASE_CURSOR_OPERATION_TIME_MS);
    pushHatCustom(Hat::LEFT, BASE_CURSOR_OPERATION_TIME_MS + 100, (box_line % BOX_COLUMN_LENGTH) + 1);
    pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
    pushButtonCustom(Button::A, BASE_CURSOR_OPERATION_TIME_MS);
}

/**
 * 孵化した手持ちのポケモンを全てボックスに預け、同時にタマゴを手持ちに加える
 *
 * @param int box_line
 * @return void
 */
void doBoxOperations(int box_line) {
    openBox();
    putInPokemons(box_line);

    // 最後の孵化のタイミングでは、タマゴの引き出しは行わない
    if (!(box_line == BOX_COLUMN_LENGTH && loopCount == BOX_COUNT - 1)) {
        pullOutEggs(box_line);
    }

    // ボックスを跨いだときは処理が重くて閉じる操作に失敗することがあるため、回避するための遅延&連打処理を用意
    if (box_line == BOX_COLUMN_LENGTH) {
        delay(4000);
        pushButton(Button::B, 100, 3);
    }

    closeBox();
}

/**
 * ライドのON/OFFを切り替える(処理落ちで稀にボタン操作を認識しないことがあるため、連打で回避を狙う)
 *
 * @return void
 */
void switchRide() {
    pushButton(Button::PLUS, 10, 6);
    delay(BASE_PUSH_BUTTON_TIME_MS);
}

/**
 * 走り回って手持ちの全てのタマゴを孵化させる
 *
 * @param long run_time_ms
 * @return void
 */
void hatchAllEggs(unsigned long run_time_ms) {
    SwitchControlLibrary().moveLeftStick(Stick::MAX, Stick::NEUTRAL);
    SwitchControlLibrary().moveRightStick(Stick::MIN, Stick::NEUTRAL);
    SwitchControlLibrary().sendReport();
    delay(500);
    run_time_ms -= 20500;
    while (600 <= run_time_ms) {
        SwitchControlLibrary().pressButton(Button::A);
        SwitchControlLibrary().sendReport();
        delay(100);
        SwitchControlLibrary().releaseButton(Button::A);
        SwitchControlLibrary().sendReport();
        delay(500);
        run_time_ms -= 600;
    }
    SwitchControlLibrary().moveLeftStick(Stick::NEUTRAL, Stick::NEUTRAL);
    SwitchControlLibrary().moveRightStick(Stick::NEUTRAL, Stick::NEUTRAL);
    SwitchControlLibrary().sendReport();
    delay(100);
    pushButton(Button::A, 500, 35);  // 最後の1匹はAボタンで孵化する
}

////////////////// execute ////////////////////////////////

void setup() {
    // Switchがマイコンを認識するまでは信号を受け付けないため、適当な処理をさせておく
    pushButton(Button::LCLICK, 500, 5);

    switchRide();
    // 念の為、正面を向かせる
    pushButton(Button::L, 2000);

    // 初回のボックス操作
    openBox(true);
    pullOutEggs(0);
    closeBox();
}

void loop() {
    // 指定のボックス分の孵化を終えた後、スリープ状態にして処理を終了する
    if (loopCount == BOX_COUNT) {
        sleepGame();
        exit(0);
    }

    for (int box_line = 1; box_line <= BOX_COLUMN_LENGTH; box_line++) {
        // タマゴの孵化
        hatchAllEggs(260000);  // 260秒で孵化サイクル40が全て孵化できる?

        // ボックス操作
        doBoxOperations(box_line);
    }

    // レポートオプション
    if (REPORT_OPTION == 1) {
        writeReport();
    }

    loopCount++;
}

2023年4月24日追記:Ver1.3.0のリリースに合わせて更新しました。それ以前からのバージョンからも不安定な面があったのですが、今回の対応によりかなり安定感が向上していると思います。一方で、パフォーマンスに関しては1ボックスあたり5秒程度多く時間がかかるようになってしまっていますが、ご理解いただければと思います。

2023年9月14日追記:Ver2.0.1用に更新しました。変更点は最初にマイコンを認識させるためのコマンドがLボタン押し込みになっていましたが、アップデートによりこちらのボタンに口笛が割り当てられるようになったためBボタンに変更しました。それ以外の中身は変更を加えておりません。小さな修正のため、旧コードは残さずそのまま上書きして公開しています。

2023年4月24日の更新内容(気になる方は展開してみてください)
・一番大きな変化として、ボックスを開く際の待機時間を1秒伸ばしました。仮説ではあるのですが、ボックスの開く時間はボックスの中身が影響する(ボックス操作オンリーで動作確認していて動いていたコードが実際に孵化を挟むことで不安定になることを確認したため)という点、また、これまた推測ではあるのですがボックスの読み込み時間は個体差が出やすい部分かと思われるので、結構余裕をもたせることにしました。

・ケーブルやハブの品質によっては初回のライドができないまま始まってしまう報告があったため、ライドまでの時間を少し伸ばしました。

・ボックス操作時に稀にコマンドの入力漏れが起きてしまう事象、また、逆にカーソル入力が多くなってしまう事象の発生を抑えました。(ボックス操作オンリーのデバッグで200BOX分、実際の孵化も含めたデバッグでも50BOX分ほど動作確認を行っていますが、少なくとも筆者の環境ではこれまで一度も再現していないです。しかし、他の環境ではまだ再現する可能性はあると思っています。)

・稀にポケモンを掴むことに失敗していたことを確認したため、猶予時間をわずかに伸ばしました。

・稀にボックスを閉じることに失敗していたことを確認したため、予備の処理を追加しました。これにより不要になった処理を一部削除しました。また、副作用として、万一ループ破綻した場合であっても、ただ孵化できずにボックスが進んでいくだけとなる動きとなる可能性が上がっており、逆にボックスを荒らしたり着せ替えを開いたりする可能性は下がっています。

・ボックスを開く際のボタン操作やレポート時のボタン操作でも入力漏れが起きにくくなるようにしました。(元からこのケースでの入力漏れは確認できていなかったのですが、副作用もないためついでに対応した形となります。)

・ポケモンを引き取る際に最短ルートで引き取るようにすることで時短になるようにしました。(理論上は預ける際も同様のことができるのですが、預ける際にかかっている時間がいい具合に遅延してくれている可能性を考慮すると、余計なことはしたくなかったので引取時のみの対応としています)これにより、ボックスを開く待機時間を1秒伸ばした部分以外の遅延は相殺できていると思います。

・次のボックスを開いた際の遅延タイミングをボックスを閉じる直前から、次のボックスを開いた直後に変更しました。これにより、ポケモンの引き取りがやや不安定だった問題を解決しています。また、この遅延時間も少し短くしました。このコードの実行中は基本的にタマゴしかない想定であり、タマゴのみであれば読み込みは早いためです。(元々過剰なくらいだったのでもっと削れるかもしれないですが、1BOX中1回しか行わない処理のため、ここを削ってもパフォーマンスに大きく影響がでないことから最小限に留めています。)

/**
 * ボックスに眠っているタマゴを順に孵化していくスケッチ
 *
 * ※このスケッチはタマゴの孵化のみでタマゴ受け取りは行いません。タマゴは事前に用意しておいてください。
 *
 * 初期条件は以下の通り
 * 1.「セルクルタウン・西」から北に向かった場所にある、オリーブころがし場の柵の中にいること(余裕があればオリーブを端のほうに動かしておくとなお良いです)
 * 2.ポケモンが連れ歩き状態でないこと
 * 3.ライド状態でないこと
 * 4.手持ちが「ほのおのからだ」の特性持ちのポケモン1匹のみであること
 * 5.孵化させたいタマゴの預けてあるボックスが最初に開くこと(複数ボックス孵化させる場合は、ボックスが連続するような配置にしておいてください)
 * 6.孵化させるボックスにタマゴを敷き詰めてあること(空きがあると不安定になるかも)
 * 7.オフライン状態であること
 * 8.本体に装着された状態のジョイコン以外にコントローラーが接続されていないこと
 * 9.「設定」から「話の速さ」を「はやい」に、「ニックネーム登録」を「しない」にしておくこと
 */

////////////////// libraries ////////////////////////////////

// ライブラリを読み込むためのコード
#include <NintendoSwitchControlLibrary.h>

////////////////// options ////////////////////////////////

// 何ボックス分ループさせるか?
const int BOX_COUNT = 5;

// 1ボックスの孵化を終えるごとにレポートを書くかどうか(書く: 1, 書かない: 0)
const int REPORT_OPTION = 0;

////////////////// constant parameters ////////////////////////////////

// Custom版のpushButton・pushHatにおける押下時間
const int CUSTOM_INPUT_TIME = 200;

// ボックスの列数
const int BOX_COLUMN_LENGTH = 6;

// ボタン押下時の標準的な待機時間
const int BASE_PUSH_BUTTON_TIME_MS = 1000;

// カーソル操作時の標準的な待機時間
const int BASE_CURSOR_OPERATION_TIME_MS = 200;
const int HALF_CURSOR_OPERATION_TIME_MS = 100;

// ポケモンやタマゴを掴み上げるのにかかる時間
const int GRAB_POKEMONS_TIME_MS = 230;

// メニューの開閉にかかる待機時間
const int MENU_OPEN_OR_CLOSE_TIME_MS = 1300;

////////////////// variable parameters ////////////////////////////////

// ループをカウントするための変数
int loopCount = 0;

////////////////// methods ////////////////////////////////

/**
 * pushButtonのカスタム版(ボックス操作時にコマンド漏れが起きることを抑制するために用意)
 *
 * @param uint16_t      button     押すボタン
 * @param unsigned long delay_time ボタンを押した後の待ち時間(1秒 = 1000)
 * @param unsigned int  loop       ボタンを押す回数(省略可、デフォルトは1)
 */
void pushButtonCustom(uint16_t button, unsigned long delay_time, unsigned int loop = 1) {
    for (unsigned int i = 0; i < loop; i++) {
        SwitchControlLibrary().pressButton(button);
        SwitchControlLibrary().sendReport();
        delay(CUSTOM_INPUT_TIME);
        SwitchControlLibrary().releaseButton(button);
        SwitchControlLibrary().sendReport();
        delay(delay_time);
    }
    delay(CUSTOM_INPUT_TIME);
}

/**
 * pushHatのカスタム版(ボックス操作時にコマンド漏れが起きることを抑制するために用意)
 *
 * @param uint8_t       hat        押す十字キーのボタン
 * @param unsigned long delay_time ボタンを押した後の待ち時間(1秒 = 1000)
 * @param unsigned int  loop       ボタンを押す回数(省略可、デフォルトは1)
 * @return void
 */
void pushHatCustom(uint8_t hat, unsigned long delay_time, unsigned int loop = 1) {
    for (unsigned int i = 0; i < loop; i++) {
        SwitchControlLibrary().pressHatButton(hat);
        SwitchControlLibrary().sendReport();
        delay(CUSTOM_INPUT_TIME);
        SwitchControlLibrary().releaseHatButton();
        SwitchControlLibrary().sendReport();
        delay(delay_time);
    }
    delay(CUSTOM_INPUT_TIME);
}

/**
 * Switchをスリープさせる
 *
 * @return void
 */
void sleepGame() {
    holdButton(Button::HOME, 1500);
    pushButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS - 200);
}

/**
 * レポートを書く
 *
 * @return void
 */
void writeReport() {
    pushButtonCustom(Button::X, MENU_OPEN_OR_CLOSE_TIME_MS);
    pushButtonCustom(Button::R, 1300);
    pushButtonCustom(Button::A, 3800);
    pushButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS - 200);
    pushButtonCustom(Button::B, MENU_OPEN_OR_CLOSE_TIME_MS);
}

/**
 * ボックスを開く
 *
 * @param bool is_init
 * @return void
 */
void openBox(bool is_init = false) {
    pushButtonCustom(Button::X, MENU_OPEN_OR_CLOSE_TIME_MS);

    // 初回のみカーソルを合わせる
    if (is_init) {
        holdHat(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
        holdHat(Hat::UP, 2000);
        pushHatCustom(Hat::DOWN, HALF_CURSOR_OPERATION_TIME_MS);
    }

    pushButtonCustom(Button::A, 4800);
}

/**
 * ボックスを閉じる
 *
 * @return void
 */
void closeBox() {
    // 極稀にボックスを閉じるのに失敗することがあるため、予備のボタン操作
    SwitchControlLibrary().pressButton(Button::B);
    SwitchControlLibrary().sendReport();
    delay(120);
    SwitchControlLibrary().releaseButton(Button::B);
    SwitchControlLibrary().sendReport();
    delay(80);

    pushButtonCustom(Button::B, 2800);
    pushButtonCustom(Button::B, MENU_OPEN_OR_CLOSE_TIME_MS);
}

/**
 * ポケモンを預ける
 *
 * @param int box_line
 * @return void
 */
void putInPokemons(int box_line) {
    // 手持ちのポケモンを範囲選択する
    pushHatCustom(Hat::LEFT, HALF_CURSOR_OPERATION_TIME_MS);
    pushHatCustom(Hat::DOWN, HALF_CURSOR_OPERATION_TIME_MS);
    pushButtonCustom(Button::MINUS, BASE_PUSH_BUTTON_TIME_MS);
    holdHat(Hat::DOWN, 1000);
    delay(100);
    pushButtonCustom(Button::A, GRAB_POKEMONS_TIME_MS);

    // 範囲選択したポケモンをボックスに預ける
    for (int i = 0; i < box_line; i++) {
        pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
    }
    pushHatCustom(Hat::UP, BASE_CURSOR_OPERATION_TIME_MS);
    pushButtonCustom(Button::A, GRAB_POKEMONS_TIME_MS);

    // ボックスがいっぱいになったら、次のボックスに移動させる
    if (box_line == BOX_COLUMN_LENGTH) {
        pushButtonCustom(Button::R, BASE_CURSOR_OPERATION_TIME_MS);
        delay(3000);  // ボックスの読み込みで重くなることを考慮して遅延させる

        // 最後のボックスである場合、その後のカーソル操作は行わない
        if (loopCount == BOX_COUNT - 1) return;

        pushHatCustom(Hat::RIGHT, HALF_CURSOR_OPERATION_TIME_MS);
    }

    // 次の処理に繋げやすくするため、カーソルを移動させる
    pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
}

/**
 * タマゴを引き出す
 *
 * @param int box_line
 * @return void
 */
void pullOutEggs(int box_line) {
    pushButtonCustom(Button::MINUS, BASE_PUSH_BUTTON_TIME_MS);
    holdHat(Hat::DOWN, 1000);
    delay(100);
    pushButtonCustom(Button::A, GRAB_POKEMONS_TIME_MS);

    const uint8_t move_direction = (box_line <= 2 || box_line == 6) ? Hat::LEFT : Hat::RIGHT;
    const int move_count = (box_line <= 2 || box_line == 6) ? (box_line % BOX_COLUMN_LENGTH) + 1 : BOX_COLUMN_LENGTH - box_line;
    for (int i = 0; i < move_count; i++) {
        pushHatCustom(move_direction, BASE_CURSOR_OPERATION_TIME_MS);
    }

    pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
    pushButtonCustom(Button::A, GRAB_POKEMONS_TIME_MS);
}

/**
 * 孵化した手持ちのポケモンを全てボックスに預け、同時にタマゴを手持ちに加える
 *
 * @param int box_line
 * @return void
 */
void doBoxOperations(int box_line) {
    openBox();
    putInPokemons(box_line);

    // 最後の孵化のタイミングでは、タマゴの引き出しは行わない
    if (!(box_line == BOX_COLUMN_LENGTH && loopCount == BOX_COUNT - 1)) {
        pullOutEggs(box_line);
    }

    closeBox();
}

/**
 * ライドのON/OFFを切り替える(処理落ちで稀にボタン操作を認識しないことがあるため、連打で回避を狙う)
 *
 * @return void
 */
void switchRide() {
    pushButton(Button::PLUS, 10, 6);
    delay(BASE_PUSH_BUTTON_TIME_MS);
}

/**
 * 走り回って手持ちの全てのタマゴを孵化させる
 *
 * @param long run_time_ms
 * @return void
 */
void hatchAllEggs(unsigned long run_time_ms) {
    SwitchControlLibrary().moveLeftStick(Stick::MAX, Stick::NEUTRAL);
    SwitchControlLibrary().moveRightStick(Stick::MIN, Stick::NEUTRAL);
    SwitchControlLibrary().sendReport();
    delay(500);
    run_time_ms -= 20500;
    while (600 <= run_time_ms) {
        SwitchControlLibrary().pressButton(Button::A);
        SwitchControlLibrary().sendReport();
        delay(100);
        SwitchControlLibrary().releaseButton(Button::A);
        SwitchControlLibrary().sendReport();
        delay(500);
        run_time_ms -= 600;
    }
    SwitchControlLibrary().moveLeftStick(Stick::NEUTRAL, Stick::NEUTRAL);
    SwitchControlLibrary().moveRightStick(Stick::NEUTRAL, Stick::NEUTRAL);
    SwitchControlLibrary().sendReport();
    delay(100);
    pushButton(Button::A, 500, 35);  // 最後の1匹はAボタンで孵化する
}

////////////////// execute ////////////////////////////////

void setup() {
    // Switchがマイコンを認識するまでは信号を受け付けないため、適当な処理をさせておく
    pushButton(Button::B, 500, 8);

    switchRide();
    // 念の為、正面を向かせる
    pushButtonCustom(Button::L, 1800);

    // 初回のボックス操作
    openBox(true);
    pullOutEggs(0);
    closeBox();
}

void loop() {
    // 指定のボックス分の孵化を終えた後、スリープ状態にして処理を終了する
    if (loopCount == BOX_COUNT) {
        sleepGame();
        exit(0);
    }

    for (int box_line = 1; box_line <= BOX_COLUMN_LENGTH; box_line++) {
        // タマゴの孵化
        hatchAllEggs(260000);  // 260秒で孵化サイクル40が全て孵化できる?

        // ボックス操作
        doBoxOperations(box_line);
    }

    // レポートオプション
    if (REPORT_OPTION == 1) {
        writeReport();
    }

    loopCount++;
}

設定の確認

基本は前回のコードと同じなのですが、若干設定が減っています。

  1. 「セルクルタウン・西」から北に向かった場所にある、オリーブころがし場の柵の中にいること
  2. ポケモンが連れ歩き状態でないこと
  3. ライド状態でないこと
  4. 手持ちが「ほのおのからだ」の特性持ちのポケモン1匹のみであること
  5. 孵化させたいタマゴの預けてあるボックスが最初に開くこと(複数ボックス孵化させる場合は、ボックスが連続するような配置にしておいてください)
  6. 孵化させるボックスにタマゴを敷き詰めてあること
  7. オフライン状態であること
  8. 本体に装着された状態のジョイコン以外にコントローラーが接続されていないこと
  9. 「設定」から「話の速さ」を「はやい」に、「ニックネーム登録」を「しない」にしておくこと

重要なものをいくつか解説します!

開始位置について

「セルクルタウン・西」から北に向かったところにある「オリーブころがし場」(この場所に正式な名前ほしい)が今回の舞台です! ジムチャレンジをやったところですね!

オリーブころがし場この中であればどこからスタートしても大丈夫な認識なのですが、ごく稀に柵を飛び越える事象を確認しているため、中心で動かすのよいと思います。

また、中央のオリーブですが、端っこの方に動かしておくとなお良いです! もしオリーブを柵の外に飛ばしてしまっても中央に復活するだけで暗転等はないため、そこは安心してください!!

端っこにオリーブを動かした状態

ボックスの状態について

タマゴは敷き詰めておいてください!
タマゴで埋まったボックス
タマゴが埋まっていなくても動作するとは思うのですが、ボックス操作が若干不安定になる可能性があります。

オプションについて

コードの26行目、29行目がオプションになります。

// 何ボックス分ループさせるか?
const int BOX_COUNT = 1;

26行目の「1」の部分が孵化させるボックス数になります。5にすると5ボックス、10にすれば10ボックス孵化させる動きをします。

// 1ボックスの孵化を終えるごとにレポートを書くかどうか(書く: 1, 書かない: 0)
const int REPORT_OPTION = 0;

29行目は1ボックス分の孵化毎にレポートを書くかどうかのオプションになります。SVではエラー落ちが多いと聞いているので、仮にエラー落ちした場合でも被害を最小限に抑えられるよう用意しています。

デフォルトでは無効にしているので、有効にする場合は「0」を「1」にしてください。

数値の編集は必ず半角で行っていただくようお願いいたします。

前回のコードと比較した改善点について

改善点として、以下のループの破綻パターンを潰せています。

  • 稀に灯台から落ちてしまう
  • キャモメとの戦闘後、稀にハシゴを降りてしまう

灯台から落ちてしまうのは認知していたのですが、キャモメとの戦闘後に灯台の中心に向かってしまいハシゴを降りてしまう事象を報告いただいていました。

今回のコードで走る場所ではポケモンが一切出ない(はず?)のため、これらのループ破綻パターンを解消しています。

それにより、炎の体ポケモンの選定がなくなったことや、孵化したポケモンに経験値が入ってしまう事象も解消できるようになりました!

オリーブころがし場は長時間動かす場合に「障害物にぶつかった際にどうするか?」という懸念もあったのですが、カメラもぐるぐるしながら孵化すれば脱出が容易であることを共有いただき、今回のコードの作成につなげることができました。(孵化中もカメラぐるぐるしているのは気になるかもですが…)
情報を共有いただきありがとうございます!
ただ、ダッシュ状態だと詰まりやすいこともあり、安定をとってダッシュはさせておりません。

ループ破綻について

手動でしか確認していないのですが、柵のポールにぶつかった際にごく稀に柵を飛び越えてしまうことを確認しています。

孵化中にはまだ確認できていないものの、発生する確率は0ではないと思われます。

ただ、灯台上とは異なり常に壁に密着しているわけではないことから発生率は低いと考えています。

最後に

今回のコードは不安要素が拭いきれていません。もし、それらの解決策など思いつくものがあれば@lefmarnaまで共有いただければと思います!

その他、ループから抜けてしまった場所があればそれも教えていただきたいです。ほとんどの場合は解決に至らないと思いますが、どんな例外が眠っているのか把握しておきたいと思っています!

スポンサーリンク