【マイコン】ポケモンSVでボックスのポケモンを逃がすコード【自動化】

ポケモンSVでボックスのポケモンを逃していくコードを作りました!

ホームとの連動が来たら逃がすのは手間ではなくなるのですが、それまでは便利に使えると思います!

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

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

関連記事

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

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

今回のコードは「NintendoSwitchControlLibrary」を使用しています。まだダウンロードされていない方は、解凍したフォルダを『Arduino』フォルダの中にある『libraries』フォルダの中にコピーするか、ライブラリマネージャよりインストールしてください。

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

今回のコードは完全な動作を約束するものではありません。

ポケモンSV特有の処理落ちや入力漏れにはなるべく対応できるよう尽力していますが、それでも止まってしまう可能性は否定できません。

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

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

ソースコード

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

Ver.1.1.0用はこちら
/**
 * 指定されたボックスの数だけポケモンを逃がすスケッチ
 *
 * 初期条件は以下の通り
 * 1.メニューを閉じた状態であること
 * 2.30匹フルにポケモンが預けられたボックスが続いた状態であること。(タマゴなど逃がせないポケモンが混じった状態はNG)
 * 3.オフライン状態であること
 * 4.本体に装着された状態のジョイコン以外にコントローラーが接続されていないこと
 * 5.「設定」から「話の速さ」を「速い」にしておくこと
 */

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

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

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

// 何ボックス分逃がすかをここで指定する
const int BOX_COUNT = 2;

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

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

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

// 1ボックスに預けられるポケモンの数
const int BOX_POKEMON_COUNT = 30;

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

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

// メニューの開閉にかかる待機時間
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);
    pushButtonCustom(Button::A, BASE_PUSH_BUTTON_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);
        pushHat(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
    }

    pushButtonCustom(Button::A, 4000);
}

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

/**
 * ポケモンを逃がす
 *
 * @return void
 */
void releasePokemon() {
    // 「にがす」を選択
    pushButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    pushHatCustom(Hat::UP, BASE_CURSOR_OPERATION_TIME_MS);
    pushHatCustom(Hat::UP, BASE_CURSOR_OPERATION_TIME_MS);
    pushButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    pushHatCustom(Hat::UP, BASE_CURSOR_OPERATION_TIME_MS);

    // 「本当に 逃がしますか?」
    pushButtonCustom(Button::A, 1500);
    pushButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
}

/**
 * 1ボックス分(30匹)ポケモンを逃がす
 *
 * @return void
 */
void releaseBox() {
    // 現在のボックスの列(左に移動するか、右に移動するかの判別に使用する)
    int box_line = 1;

    for (int i = 1; i <= BOX_POKEMON_COUNT; i++) {
        // ポケモンを逃がす
        releasePokemon();
        // 次のポケモンにカーソルを合わせる
        if (i % BOX_COLUMN_LENGTH == 0) {
            pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
            box_line++;
        } else if (box_line % 2 == 1) {
            pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
        } else {
            pushHatCustom(Hat::LEFT, BASE_CURSOR_OPERATION_TIME_MS);
        }
    }

    // 次のボックスに移動する
    pushButtonCustom(Button::R, BASE_CURSOR_OPERATION_TIME_MS);
    delay(4000);  // ボックスの読み込みで重くなることを考慮して遅延させる
    pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
    pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
    pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
    pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
}

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

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

    // マイコンを認識したら、ボックスを開く
    openBox(true);
}

void loop() {
    // 指定されたボックス分のポケモンを逃し終えたら処理を終了する
    if (loopCount == BOX_COUNT) {
        closeBox();
        sleepGame();
        exit(0);
    }

    releaseBox();
    loopCount++;
}

2023年3月4日追記:Ver1.2.0用に更新しました。安定感を向上させています。

2023年9月14日追記:Ver2.0.1用に更新しました。変更点は最初にマイコンを認識させるためのコマンドがLボタンになっていたのをより副作用の少ないBボタンに変更したのみで中身に変更は加えていません。そのため、旧コードは残さず、そのまま上書きして公開しています。

/**
 * 指定されたボックスの数だけポケモンを逃がすスケッチ
 *
 * 初期条件は以下の通り
 * 1.メニューを閉じた状態であること
 * 2.30匹フルにポケモンが預けられたボックスが続いた状態であること。(タマゴなど逃がせないポケモンが混じった状態はNG)
 * 3.オフライン状態であること
 * 4.本体に装着された状態のジョイコン以外にコントローラーが接続されていないこと
 * 5.「設定」から「話の速さ」を「速い」にしておくこと
 */

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

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

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

// 何ボックス分逃がすかをここで指定する
const int BOX_COUNT = 2;

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

// Custom版のpushHatにおける押下時間
const int CUSTOM_INPUT_TIME_MS = 150;

// Custom版のholdButtonにおける押下後の追加の待機時間
const int HOLD_BUTTON_ADD_INTERVAL_TIME_MS = 100;

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

// 1ボックスに預けられるポケモンの数
const int BOX_POKEMON_COUNT = 30;

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

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

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

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

int loopCount = 0;

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

/**
 * holdButtonのカスタム版(hold後の待機時間を一律で伸ばす目的で用意)
 */
void holdButtonCustom(uint16_t button, unsigned long hold_time) {
    holdButton(button, hold_time);
    delay(HOLD_BUTTON_ADD_INTERVAL_TIME_MS);
}

/**
 * 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_MS);
        SwitchControlLibrary().releaseHatButton();
        SwitchControlLibrary().sendReport();
        delay(delay_time);
    }
    delay(CUSTOM_INPUT_TIME_MS);
}

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

/**
 * ボックスを開く
 *
 * @param bool is_init
 * @return void
 */
void openBox(bool is_init = false) {
    holdButtonCustom(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);
    }

    holdButtonCustom(Button::A, 4200);
}

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

/**
 * ポケモンを逃がす
 *
 * @return void
 */
void releasePokemon() {
    // 「にがす」を選択
    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    pushHatCustom(Hat::UP, BASE_CURSOR_OPERATION_TIME_MS);
    pushHatCustom(Hat::UP, BASE_CURSOR_OPERATION_TIME_MS);
    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    pushHatCustom(Hat::UP, BASE_CURSOR_OPERATION_TIME_MS);

    // 「本当に 逃がしますか?」
    holdButtonCustom(Button::A, 1700);
    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
}

/**
 * 1ボックス分(30匹)ポケモンを逃がす
 *
 * @return void
 */
void releaseBox() {
    // 現在のボックスの列(左に移動するか、右に移動するかの判別に使用する)
    int box_line = 1;

    for (int i = 1; i <= BOX_POKEMON_COUNT; i++) {
        // ポケモンを逃がす
        releasePokemon();
        // 次のポケモンにカーソルを合わせる
        if (i % BOX_COLUMN_LENGTH == 0) {
            box_line++;
            if (i != BOX_POKEMON_COUNT) {
                pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);  // 最後のポケモンの場合は入力不要
            }
        } else if (box_line % 2 == 1) {
            pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
        } else {
            pushHatCustom(Hat::LEFT, BASE_CURSOR_OPERATION_TIME_MS);
        }
    }

    // 次のボックスに移動する
    holdButtonCustom(Button::R, BASE_CURSOR_OPERATION_TIME_MS);
    delay(4000);  // ボックスの読み込みで重くなることを考慮して遅延させる
    pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
    holdHat(Hat::UP, 3000);
    delay(HOLD_BUTTON_ADD_INTERVAL_TIME_MS);
    pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
}

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

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

    // マイコンを認識したら、ボックスを開く
    openBox(true);
}

void loop() {
    // 指定されたボックス分のポケモンを逃し終えたら処理を終了する
    if (loopCount == BOX_COUNT) {
        closeBox();
        sleepGame();
        exit(0);
    }

    releaseBox();
    loopCount++;
}

設定の確認

マイコンを繋げる前に、以下の状態になっていることを確認してください。

  1. メニューを閉じた状態であること
  2. 30匹フルにポケモンが預けられたボックスが続いた状態であること。(タマゴなど逃がせないポケモンが混じった状態はNG)
  3. オフライン状態であること
  4. 本体に装着された状態のジョイコン以外にコントローラーが接続されていないこと
  5. 「設定」から「話の速さ」を「速い」にしておくこと

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

ボックスの状態について

ポケモンを敷き詰めておいてください!

ボックスの初期状態

ボックスに1つでも空きがあったりタマゴが混じっていたりすると正常に動作しません。動作が止まるだけでなく予期しない操作を行う可能性もありますので絶対にやめましょう!!

オプションについて

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

// 何回ループさせるか?
const int BOX_COUNT = 2;

20行目の「2」の部分に逃がすボックス数を指定します。

2であれば60匹、5にすれば150匹のポケモンを逃がします。

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

動作について

以下のように動作していれば正常です。

  1. ボックスを開く
  2. ボックスのポケモンを全て逃がす
  3. 次のボックスへ移動する
  4. 指定されたボックスの数だけ、②と③を繰り返し
  5. ボックスを閉じる
  6. Switchをスリープする

最後に

今回のコードはそれなりに安定するとは思います。今の形になってから16box連続の逃がしを実行したのですが、止まることなく動作してくれました。

ただ、ポケモンSVは自動化が不安定なことも認識しており、もしループを抜けてしまう事象など見つけましたら@lefmarnaまで共有いただければと思います!

スポンサーリンク