【Arduino自動化16】自動ミラクル交換(ID集めほか)

ますたーです。こんにちは。

今回は、自動でボックスのポケモンをマジカル交換(旧:ミラクル交換)に出し続けます。

※本記事ではポケモンを「交換に出します」。利用に際してはくれぐれも自己責任でお願いします

 Arduino自動化記事はこれで第16回。これからもよろしくお願いします。

 

なお、Arduino Leonardo自動化の導入・機材構成については導入記事を参考にしてください。

導入記事:【Arduino自動化01】Arduino開発環境の導入

※本ブログに初めてお越しの方は「本ブログについて」もぜひ、ご覧ください。

 

概要

本記事では、YY通信のマジカル交換(旧:ミラクル交換)を自動で繰り返し行います。「孵化余り」の有効活用や、ロトミIDくじ用のID集めなどにお役立てください。

※2021年2月19日AM9時追記:本稿中の「ミラクル交換」はすべて「マジカル交換」の誤りです。過去作品に引っ張られました。以降、適宜読み替えてください

f:id:tangential_star:20210213121954g:plain
f:id:tangential_star:20210213121616p:plain
マジカル交換(ミラクル交換)を自動で行う。待機時間など含め1回あたり約2分20秒ほど

では、目次です。

ソースコードのみ欲しいという方はどうぞコチラから⇒読み飛ばす

 

ラクル交換でID集め(ロトミIDくじを効率的に)

ラクル交換の大きな特徴は、何よりも交換先が全国不特定多数のポケモントレーナーであることです。ポケモン剣盾での「おやID」は6桁なので、32ボックス960匹をランダムに交換し続けたとしても、これらの間で重複が起こる可能性はほぼ0です

※無作為の960匹について、これらでIDが重複する確率は単純計算で 1-(1-1/106)960=0.09595%

おやIDをたくさん集めることで、ロトミIDくじの当選確率もアップします。例えば、50種類のおやIDを入手しておくと「はずれ」が出る確率が1%を下回ったり、100種類のおやIDがあれば50%以上の確率で「ポイントアップ」が手に入るようになったりと、非常にお得です。【Arduino自動化14】ロトミIDくじ無限抽選では、このあたりの、IDの数と当選確率の話も纏めているので、合わせてご参考いただければと思います。

f:id:tangential_star:20210204135040g:plain
f:id:tangential_star:20210203180947p:plain
たくさんのIDがあれば、ロトミIDくじ当選確率もアップ!

要するに、ミラクル交換で他人産IDのポケモンを50匹程度を集めておけば、ロトミIDくじが、まさにソシャゲの「ログインボーナス」のようになるのでおすすめです。

ほとんど毎日「ポイントアップ」が貰えて、時々「ポイントマックス」が貰えるとなれば、毎日のポケモンのモチベーションが大きく上がりますよね。

 

他人の孵化余りも手に入る

ラクル交換のもう一つの魅力、それは、孵化余りを流すプレイヤーも多いということ。私も含め、孵化余りは積極的にGTSポケモンHOMEでの交換、ミラクル交換に出していますが、ID集めはもちろん、これは何よりも他人の孵化余りを手に入れられるから。

現在、環境で流行っているポケモンの孵化余りが手に入ったり、有用なタマゴわざを持ったポケモンうたかたのアリア持ちラプラス、じばく持ちゴンベなど)などが手に入りやすく、対戦ガチ勢にも嬉しい仕様ですね。

f:id:tangential_star:20210218224023p:plain
f:id:tangential_star:20210218224028p:plain
有用なタマゴわざを覚えていたり、希少価値の高いオシャボに入っていたりも多い

つまりは、個体値ポケモン夢特性ポケモンタマゴわざを持っているポケモンおしゃれボールに入ったポケモンなどを手に入れることもできます。特に海外産ポケモンも手に入りやすく、これらを活用するとタマゴ孵化による色違い厳選もはかどります

ちなみに、全自動タマゴ孵化については本ブログでも取り扱っています。合わせてご参考いただければ幸いです⇒【Arduino自動化12】全自動タマゴ受け取り&孵化

 

準備(プログラムの書き換え)

ラクル交換をたくさん行うにあたっては、大量のポケモン準備する必要があります。また、効率的に通信交換をするためにも、図鑑は完成させておくと良いでしょう。

さて、本稿で扱う自動化プログラムについても、必要に応じて4箇所書き換える必要があります。

通信交換するポケモンの数

1箇所目は、何匹のポケモンを交換に出すかです。MAX_TRADE_POKEMONにて指定してください。必ずしも30の倍数である必要はありませんが、左詰めにあずけておいてください。

#define MAX_TRADE_POKEMON (30*3-5)
// ★何匹通信交換するか?【重要】

ラクル交換の待ち時間

2箇所目は、ミラクル交換の待機時間です。具体的には、YY通信で「ミラクル交換」を選んでフィールドに戻ってから「交換完了!」の文字が出るまでの待機時間です。

概ね30秒あればマッチングすることが多いですが、時々マッチングに時間がかかり、50秒ほどかかることもあります。皆様の環境に合わせて数字を記入してください。

数字は、WAIT_TIME_FOR_MATCHINGに単位「秒」で記入してください。

#define WAIT_TIME_FOR_MATCHING (45) 
// ★通信交換を何秒待つか?(単位:秒)

なお、この時間指定が短すぎると、その次のループでうまく交換ができませんので注意しましょう。45秒で設定した筆者実証時には、85回の交換中3回、45秒以内での通信交換に失敗しています。もし、時間に余裕のある人は、60秒などの充分に大きな数字を設定しておくと良いかもしれません。

図鑑の状況と通信進化ポケモン

3箇所目・4箇所目は、ポケモン図鑑の完成状況(新たに登録される可能性があるかどうか)と、受け取ったポケモンが進化する可能性を考慮するかどうかです。これらは数字ではなく、trueまたはfalseを記入します。

ポケモン図鑑が完成している場合はPOKEDEX_COMPLETEDにtrueを指定してください。また、通信交換で進化するポケモンを無視する場合はIGNORE_EVOLVEをtrueにしてください。デフォルトでは、ポケモン図鑑は完成済(true)・通信進化ポケモンは無視しない(false)になっています。

 

#define POKEDEX_COMPLETED (true) 
// ★ポケモン図鑑は完成済か?【重要】

 

#define IGNORE_EVOLVE (false)
// ★通信進化ポケモンは来ない前提とするか?【重要】

 

ポケモン図鑑に登録されていないポケモンを交換で受け取った場合、その図鑑説明の画面が出てきます。こちらは、たかだか5秒程度のロスなので、図鑑完成していない人も、あまり気にならないかもしれません。

一方で、バケッチャパンプジンに進化)などの通信進化するポケモンを受け取った場合、そのポケモンの進化画面が発生してしまいます。この進化ポケモン、数は20数匹と多めで、意外と受け取る機会が多いです。

通信進化は長くて20秒は時間を取られるので、よほど受け取らない自信が無い限りは、IGNORE_EVOLVEをfalseにしておきましょう。

特に、稀有な機会ではありますが、メタルコート持ちストライクの場合、進化時に「バレットパンチ」を覚えようとするため、そういうところもケアする必要があります。

f:id:tangential_star:20210218233952g:plain

バケッチャなど、通信交換で進化するポケモンを受け取る機会は意外と多い。注意しよう

 

実際に自動化ミラクル交換を試してみた

早速、85匹のパッチルドンを用意して、ミラクル交換のArduino自動化を実装&試してみました。

 

f:id:tangential_star:20210218234741g:plain
f:id:tangential_star:20210218234922g:plain
85匹のパッチルドンでミラクル交換!所要時間3時間20分の大半は待ち時間だ

設定は1試行あたり45秒の待機時間ポケモン図鑑は完成済、進化ポケモンは無視しない想定で、かかった時間は約3時間20分でした。1回の交換にかかる時間を平均すると約2分20秒となります。

なお、特筆すべき点として、待機時間を45秒に設定で、85回の通信交換中、3回はこの時間内に通信交換が成立せず、それぞれの翌試行分の交換が失敗しています。ゆえに、通信交換に成功したのは82匹です(いずれの試行も、通信交換に失敗した次の試行でループが復帰していました)。一応、これを避けるためには、待機時間を冗長に設定する他はありません。体感としては、60秒ほどあれば通信交換がほぼ成立すると考えます。

ちなみに、82匹の交換が成立したわけですが、そのうち1回のみ、通信交換するポケモンバケッチャ)を受け取っています。約80回に1回(確率にすれば約1.25%)ですし、もし「全体的に時間効率を高めたい」という人は、IGNORE_EVOLVEを(true)にしても良いかもしれません。

f:id:tangential_star:20210218234725g:plain

ラクル交換後のボックス。オシャボ入が7匹、準伝説3匹など上々の結果

 

ソースコード

さて、実際に使ったソースコードです。

使い方は簡単。ボックスを左詰めにした後、ポケモン剣盾をインターネット接続し、Arduinoを挿し込むだけ。

ただし、前述の通り、4箇所は各人のステータスに応じて書き換えてください。

/* 
 *  マジカル交換自動化
 *  
 *  ボックスは交換を始める先頭&左詰めにしておくこと。
 *  野生ポケモンが近くに発生しない環境で使うこと!
 *  (重くならない、ワイルドエリア「外」+オブジェクトが少ない場所を推奨)⇒シュートシティのタワー前とか?
 *  あとはYY通信がオンラインになっている状態でArduino差し込むだけ!
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h>

#define HOLDTIME (95) // 1回のキー入力の長押し時間


// 実測値メモ 45秒指定で、85匹交換=合計3時間14分45秒[1試行平均137秒]
// [うち、交換失敗3回・改造と思しき個体3匹]

#define MAX_TRADE_POKEMON (30*3-5)
// ★何匹通信交換するか?【重要】
// ボックスの中で交換するポケモンの数を入れましょう。
// ※無論、ボックスの中身は左詰めでお願いします。

#define WAIT_TIME_FOR_MATCHING (45) 
// ★通信交換を何秒待つか?(単位:秒)
// 実際の待ち時間が上記指定時間をを超えた場合など、短すぎるとずれる可能性あり。
// 2順目でズレを吸収し、3順目から正式に進行するはずだが、この事象が発生したタイミング
// 如何によってはボックスの遷移が正しく行われない可能性あり。
// ※時間に余裕がある場合は、確実に交換できるよう「充分に長い時間(75など)」にしておくと安心。
// 特に深夜帯25:30を回ると45では心許なくなる。

#define POKEDEX_COMPLETED (true) 
// ★ポケモン図鑑は完成済か?【重要】
// 完成済の人は「true」にしてください
// 新規ポケモン受取時に図鑑登録ダイアログ出現を考慮するかどうかです。
// ※未完成の挙動は筆者未検証です!(そのへんの動画見て時間合わせしてるので動かないかも)

#define IGNORE_EVOLVE (false)
// ★通信進化ポケモンは来ない前提とするか?【重要】
// 進化後のポケモン例:フーディン・カイリキー・ゲンガー・ヤドキング・ニョロトノ・ハガネール・ハッサム・
// キングドラ・ポリゴン2・ミロカロス・ポリゴンZ・ドサイドン・エレキブル・ブーバーン・ヨノワール・
// ギガイアス・ローブシン・フレフワン・ペロリーム・オーロット・パンプジン・シュバルゴ・アギルダー
// ※コイツら↑を受け取らない前提なら「true」にしてください(非推奨)
// ※参考までに、筆者は本デバッグ中にバケッチャを受け取りました(パンプジンに進化しました)。わずか33試行目でした。


void PushRL(int delay_time_ms);
int PushKey(char* keyname, int holdtime, int delaytime);
void NextDayInCheatMode(void);

void YYconnection_start(int which_pokemon);
void YYconnection_receive(void);

void setup() {
  // コントローラーとして認識されるためにRLを7回ほどカチャカチャする
  for(int i=0;i<7;i++)PushRL(300);
  delay(1000);
}


void loop() {
  int num_pokemon; // 規定回数入れ替えたら
  for(num_pokemon=1; num_pokemon <= MAX_TRADE_POKEMON ; num_pokemon++){
    YYconnection_start(num_pokemon);
    delay( (unsigned long int)WAIT_TIME_FOR_MATCHING*1000UL );
    YYconnection_receive();
  }
  for(;;) delay(100); // プログラム終了
}

void PushRL(int delay_time_ms){
  SwitchControlLibrary().PressButtonR();
  SwitchControlLibrary().PressButtonL();
  delay(HOLDTIME);
  SwitchControlLibrary().ReleaseButtonR();
  SwitchControlLibrary().ReleaseButtonL();
  delay(delay_time_ms);
  return;
}
int PushKey(char* keyname, int holdtime, int delaytime){
  // ホームボタン・方向キーはRight, Left, Up, Down, Homeなど2文字以上で入力。
  // その他ボタン入力は1文字(A,B,X,Y,R,L,+,-)ZR・ZLにも対応
  // 同時押しは非対応
  
  if(strlen(keyname)==1){
    switch(keyname[0]){
      case 'A': case 'a': // A
        SwitchControlLibrary().PressButtonA(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonA(); delay(delaytime);
      break;
      case 'B': case 'b': // B
        SwitchControlLibrary().PressButtonB(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonB(); delay(delaytime);
      break;
      case 'X': case 'x': // X
        SwitchControlLibrary().PressButtonX(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonX(); delay(delaytime);
      break;
      case 'Y': case 'y': // Y
        SwitchControlLibrary().PressButtonY(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonY(); delay(delaytime);
      break;
      case 'L': case 'l': // L
        SwitchControlLibrary().PressButtonL(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonL(); delay(delaytime);
      break;
      case 'R': case 'r': // R
        SwitchControlLibrary().PressButtonR(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonR(); delay(delaytime);
      break;
      case 'H': case 'h': // Home
        SwitchControlLibrary().PressButtonHome(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
      break;
      case '+': case 'p': case 'P': // Plus
        SwitchControlLibrary().PressButtonPlus(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonPlus(); delay(delaytime);
      break;
      case '-': case 'm': case 'M': // Minus
        SwitchControlLibrary().PressButtonMinus(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonMinus(); delay(delaytime);
      break;
      default:
      break;
    }
  }else if(strlen(keyname)>=2){
    switch(keyname[0]){
      case 'z': case 'Z': // ZR/ZL
        if(keyname[1]=='R'||keyname[1]=='r'){
          SwitchControlLibrary().PressButtonZR(); delay(holdtime);
          if(holdtime>0)SwitchControlLibrary().ReleaseButtonZR(); delay(delaytime);
        }
        if(keyname[1]=='L'||keyname[1]=='l'){
          SwitchControlLibrary().PressButtonZL(); delay(holdtime);
          if(holdtime>0)SwitchControlLibrary().ReleaseButtonZL(); delay(delaytime);
        }
      break;
      case 'r': case 'R': // right
        SwitchControlLibrary().MoveHat(2); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'l': case 'L': // left
        SwitchControlLibrary().MoveHat(6); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'u': case 'U': // up
        SwitchControlLibrary().MoveHat(0); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'd': case 'D': // down
        SwitchControlLibrary().MoveHat(4); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'H': case 'h': // Home
        SwitchControlLibrary().PressButtonHome(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
      default:
      break;  
    }
  }else{
    return -1;
  }
  return strlen(keyname);
}
void NextDayInCheatMode(){
  // ★日付変更
  // Homeボタンを押して設定の画面へ移動
  PushKey("Home", HOLDTIME, 1000);

  // Home画面で「設定」を選ぶ
  PushKey("down", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 1500);

  // 設定画面で「日付と時刻」を開く
  PushKey("down", 1500, 105); // 1.5秒「↓」長押し
  PushKey("right", HOLDTIME, 105); 

  for(int i=0; i<4; i++){
    PushKey("down", HOLDTIME, 55);
  }
  PushKey("A", HOLDTIME, 500);

  // 時間設定(現在の日付と時刻を選ぶ)
  PushKey("down", HOLDTIME, 105);
  PushKey("down", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 500);

  // 時刻設定(日付の部分のみ回していく
  // 時間の変更
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("up",    HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("Home", HOLDTIME, 2000);
  PushKey("A", HOLDTIME, 1000);
  return;
}

void YYconnection_start(int which_pokemon){
  // (注)which_pokemonはボックスの何匹目を交換に出すか?
  // 1 <= which_pokemon <= 30 (0<29ではないので注意)

  static int shift_box = 0; // ボックスで「R(次のボックスに変える)」を押すタイミング見計らい
  
  PushKey("Y", HOLDTIME, 1300);
  PushKey("Down", HOLDTIME, 700);
  PushKey("A", HOLDTIME, 2700);

  int col=0, row=0;
  col = (which_pokemon -1) % 6; 
  row = ((which_pokemon -1)%30) / 6;

  // 31匹目以降は必要に応じてRキーで次のボックスを参照する
  if((which_pokemon -1) / 30 == 1+shift_box){
    PushKey("R", HOLDTIME, 1600);
    shift_box++;
  }

  for(int i = 0 ; i < col ; i++ ){
    PushKey("Right", HOLDTIME, 170);
  }
  for(int i = 0 ; i < row ; i++ ){
    PushKey("Down", HOLDTIME, 170);
  }
  PushKey("A", HOLDTIME, 700);  // ○○を どうしますか?
  PushKey("A", HOLDTIME, 6000); // ⇒えらぶ ~ レポートを書いて マジカル交換を 始めても よろしいですか?
  PushKey("A", HOLDTIME, 900);  // ⇒はじめる ~ 通信できる 人を 探します! ▼
  PushKey("B", HOLDTIME, 900);  // 通信する 項目を 選ぶと 探すのを キャンセルできます ▼
  PushKey("B", HOLDTIME, 3000); // レポートをかいています。
  
  return;  
}
void YYconnection_receive(void){
  delay(100);
  PushKey("Y", HOLDTIME, 1300);
  PushKey("B", HOLDTIME, 1000); // ループ破綻防止用(想定待ち時間を超えて受け取り損ねた場合のケア)
  PushKey("B", HOLDTIME, 1000); // 予備

  // 通信交換(27秒待つ)
  delay(27*1000UL);

  // 通信進化ポケモン受取時のループ破綻防止   
  if(!IGNORE_EVOLVE){
    delay(6*1000UL); // 暗転画面で6秒
    delay(7*1000UL); // 鳴き声含め5秒
    PushKey("A", HOLDTIME, 13000); // ……おや!? ○○の 様子が……! ▼ 
    PushKey("A", HOLDTIME, 3500);  // おめでとう! ○○は ○○に 進化した!▼

    // 進化時にわざを覚えるポケモン(ハッサム=バレットパンチなど)対応
    PushKey("B", HOLDTIME, 1100);    // ○○は 新しく ○○を 覚えたい…… ▼
    PushKey("Down", HOLDTIME, 1100); // ○○の かわりに 他の 技を 忘れさせますか?
    PushKey("A", HOLDTIME, 1400);    // ⇒忘れさせない
    PushKey("B", HOLDTIME, 3000);    // ○○は ○○を 覚えずに 終わった! ▼
    
    delay(4*1000UL); // 暗転終了後、明転して次画面に
  }
  PushKey("B", HOLDTIME, 300); // 予備
  PushKey("B", HOLDTIME, 300); // 予備
  PushKey("B", HOLDTIME, 300); // 予備
  delay(2*1000UL); // 予備
  if(POKEDEX_COMPLETED) return; // ポケモン図鑑完成済なら以降のダイアログ出現なし

  PushKey("B", HOLDTIME, 1000); // ○○の データが 新しく ポケモン図鑑に 登録されます!▼
  PushKey("B", HOLDTIME, 2000);
  if(!IGNORE_EVOLVE){ // 進化ポケモンも未登録の場合
    PushKey("B", HOLDTIME, 3000);
    PushKey("B", HOLDTIME, 1000); // ○○の データが 新しく ポケモン図鑑に 登録されます!▼
    PushKey("B", HOLDTIME, 2000);
  }
  PushKey("B", HOLDTIME, 300); // 予備

  return;
}

 

あとがき 

今回は16回目のArduino自動化記事として、ミラクル交換の自動化を紹介しました。

自分で実装してみて、思った以上にデバッグが大変だと思いました。長時間放置することが前提のプログラムゆえに、挙動を見守るのが大変で、コーディングをする時間よりも、本当に動くのか?を見守る時間のほうが10倍くらい長かった気がします。

私の本職は、プログラマでもデバッガーでもSEでも無いですが、本当にデバッグという作業は心がポキっと行くんだろうな、と漠然と感じました。

 

さて、話が逸れましたね。

今回のミラクル交換の実装により、今まで作ったプログラムが更に相互に利用しやすくなったな、と感じます。

具体的には、下記のようなサイクルでポケモンを遊び続けられるなぁと。

  1. 【Arduino自動化12】全自動タマゴ受け取り&孵化で色違い厳選など
  2. タマゴ孵化を経て、余ったポケモンたちを他人産ポケモンと交換(本稿)
  3. 集まったIDを使って【Arduino自動化14】ロトミIDくじ無限抽選
  4. 抽選後、不要なポケモン【Arduino自動化13】全自動ポケモン逃がしでリリース
  5. オシャボや海外産ポケモンを活用し、タマゴ孵化厳選へ(1に戻る)

ほかにも、育成用アイテムは例えば【Arduino自動化11】5番道路で全自動羽集めなどで端数の努力値振りも対応できますし、「とくせいパッチ」などは【Arduino自動化06】完全放置「マックスこうせき」集め【ダイマックスアドベンチャー】で手に入れられるんですよね。

そういう意味で、Arduinoの自動化は、ポケモンの遊び方を深めることも、作業の代替もできるんだな、と改めて感じました。

 

皆様においても、このブログの記事ほかがお役立ていただければ幸いです。

 

ではではc⌒っ.ω.)っ

 

前記事:【Arduino自動化15】ラテラルタウン掘り出し物自動購入

次記事:【Arduino自動化17】ふしぎなおくりもの受け取り【あいことば自動入力】

導入記事:【Arduino自動化01】Arduino開発環境の導入