【Arduino自動化14】ロトミIDくじ無限抽選

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

今回は、いわゆるランクマバグ状態で「ロトミIDくじ」を引き続ける自動化記事です。

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

 

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

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

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

 

概要

本記事では、ランクマバグ状態を利用した、ロトミIDくじ無限抽選を実装します。
※便宜上「ランクマバグ」と表記しますが、当ブログではランクマッチを使用しません。通信切断・初手降参の推奨もしません。

【!】ロトミIDくじは、その特性上、大量の「レポート」を伴います。必然的にmicroSDや本体メモリへの書き込みが増えますので、セーブデータ破損のリスクや動作不良のリスクなど、充分に危険性を理解した上でご参考ください。

 

NintendoSwitch Ver.11.0アップデート対応済です。

f:id:tangential_star:20210130135029g:plain
f:id:tangential_star:20210201212827p:plain
無限にIDくじを引き続けることができる。目指せ、特賞「マスターボール

では、目次です。確率計算とかもしました!(理系っぽい!)

ロトミIDくじの当選確率を知りたいという方はコチラ⇒読み飛ばす

ソースコードだけほしい方はコチラ⇒読み飛ばす

 

ポケモンセンターでロトミIDくじが引ける

まずは前提、というか余談です。

ポケモン剣盾では、ポケモンセンター各店舗のパソコンライクな機械「ロトミ」で1日1回「IDくじ」を引くことができます。

「IDくじ」をご存じない方にも説明をすると、手持ち・ボックスにいる全ポケモンの親IDと、ランダムで発生した5桁の乱数とを比較し、一致する桁数に応じて賞品が貰える、という毎作品お馴染みのおまけ要素です。

古くは第二世代(金銀)時代に始まり、ID完全一致の特等景品「マスターボール」は当時からの憧れの的でした(当時は、1日1回ではなく1週間に1回だったので引ける回数は大きく増えましたね)。完全に余談ですが、この「IDくじ」、何故か第五世代(BW)だけなかったんですよね。毎日のゲームモチベが減ったものです。

f:id:tangential_star:20210202103550g:plain
f:id:tangential_star:20210202085422p:plain
ポケモンRSEではミナモデパートでIDくじを引くことができた

前作までは、わざわざコガネシティ(ラジオとう)やミナモシティ(ミナモデパート)、コトブキシティ(テレビコトブキ)などの街に移動する、いわゆる「IDくじのための移動」が必要でした。

今作は、なんと、どこの街のポケモンセンターでもIDくじが引けますDLCでも、鎧の孤島なら拠点にロトミを連れてくることができるため、大変手軽にくじを回すことができるようになりました。また、今作のIDくじについても例外なく、特等(=ID下5桁一致)の賞品は「マスターボール」になります。

 

ロトミIDくじで特等「マスターボール」を狙う

ロトミIDくじは、ランダムに生成された5桁の数字を、手持ち・ボックスの全ポケモン(ポケジョブ除く)の親IDそれぞれの下5桁とを比較し、一致した桁数に応じて賞品がもらえます。

さて、肝心の特等「マスターボール」ですが、前述の通り親IDの下5桁が完全一致する必要があります。親IDが1つの場合(例えば一切他人と交換していない場合)の「マスターボール」当選確率を計算すると、「10の5乗分の1」になります。すなわち、わずか0.001%です

※一説によれば、今作は先頭5桁の一致も含むという噂?があるようです。それに則れば単純計算で当選確率は2倍になりますが、本ブログでは7回「特等」を当選していますが「上5桁一致」での当選はありませんでした。そのため、下5桁のみ当選する前提にて確率計算・表記しています

※ところが、後述の通り、実測値で計算した確率が「理論値の2倍」になります

f:id:tangential_star:20210203092440p:plain
f:id:tangential_star:20210203092444p:plain
f:id:tangential_star:20210203092449p:plain
「特等」当選時のメンバーの一例。いずれも下5ケタ一致だ(205575, 101082, 590328
f:id:tangential_star:20210203003331g:plain
f:id:tangential_star:20210203003339g:plain
f:id:tangential_star:20210203003348g:plain
筆者は7回「特等」を引いたが、いずれも「下5ケタ すべてが おんなじロ」と評される

 

ロトミIDくじの当選確率

上述の通り、「マスターボール」の当選確率はただでさえ低いですので、ミラクル交換やポケモンHOME(スマホ版)の交換機能などを活用し、できるだけ親IDを集めておくと良いでしょう。

ボックスいっぱいにすると30匹×32ボックス=960匹預けることができますので、これらのIDに一切の重複が無いと仮定すれば、マスターボールの当選確率が約1%まで跳ね上がります

一応、理論値としては、手持ち6匹+960匹すべて違うポケモンで0.9614%が上限です。

300匹で特等の確率は0.3%程度になるので、マスターボール狙いなら、少なくとも10ボックス程度は他人の親IDのポケモンで埋めておきたいです。どうしても難しい場合でも、ハズレの確率が1%を下回る50種類までは少なくとも集めておきたいです。

なお、IDの一致桁数に応じた賞品の当選確率は下記の通りです(筆者試算)。

 

所持ID数と各賞品の当選確率(小数点第3位を四捨五入)
ID数 【特等】5桁
マスターボール
【1等】4桁
ふしぎなあめ
【2等】3桁
ポイントマックス
【3等】2桁
ポイントアップ
【4等】1桁
モーモーミルク
はずれ
1 0.00% 0.01% 0.09% 0.90% 9.00% 90.00%
10 0.01% 0.08% 0.81% 7.75% 52.41% 38.94%
25 0.02% 0.20% 2.00% 18.00% 70.31% 9.46%
50 0.05% 0.40% 3.95% 31.96% 62.74% 0.90%
100 0.10% 0.80% 7.71% 50.90% 40.48% 0.01%
300 0.30% 2.36% 21.01% 69.69% 6.64% 0.00%
500 0.50% 3.90% 31.85% 62.66% 1.09% 0.00%
700 0.70% 5.41% 40.65% 53.07% 0.18% 0.00%
960 0.96% 7.32% 49.59% 42.11% 0.02% 0.00%
966 0.96% 7.37% 49.77% 41.89% 0.02% 0.00%

 

所持ID数と各賞品の当選確率のグラフ

狙う賞品によって最適なID数は違うが、はずれが出にくくなる50種は少なくとも欲しい

f:id:tangential_star:20210208165840p:plain

特等(マスターボール)・1等(ふしぎなあめ)は、ID数に応じて当たりやすくなる

言わずもがな、ポイントマックスはもちろんマスターボールやふしぎなあめの当選確率は、持っているID数が多ければ多いほど上がります。ユニークなのはポイントアップとモーモーミルクの当選確率で、途中で極大値を取ります。

具体的には、ポイントアップは283匹の時に69.764%、モーモーミルクは28匹の時に70.504%でそれぞれ極大値を取ります。現実的に960匹違うIDでボックスを埋めるのはツライと思いますので、このあたりを参考に、狙う道具を検討しつつ、事前に用意するポケモンの数を検討すべしですね。

 

マスターボール」の当選確率は2倍?

9時間46分放置でマスターボールを7個入手しました

筆者は過去作でGTS交換したものなども含めて12ボックス分(360匹)、ポケモンHOMEで連れてきて、自動化プログラムを使用しました。結果、プログラムを9時間46分稼働させ続け、推定試行回数888抽選で「マスターボール」7つを入手できました。

※プログラムソースコード後述します

f:id:tangential_star:20210204135040g:plain
f:id:tangential_star:20210130135029g:plain
他人産ポケモンで12ボックスを埋めて、いざ自動化!
f:id:tangential_star:20210204130023p:plain
f:id:tangential_star:20210204130028p:plain
9時間46分稼働させ、「マスターボール」7個を入手(左:実施前/右:実施後)

マスターボールの当選確率を計算(考察)

さて、確率計算です。完全に余談ですので、読み飛ばしOKです→読み飛ばす

888試行に対して7回の「特等」当選ですが、これは確率に表すと0.7883%になります。一方、12ボックス分(360匹)での、1試行あたりの入手確率は計算上、0.3593%になります。すなわち、実績値が理論値のおおよそ2倍になるという、かなり稀有な事象が起こっています。

もし、噂通り「上5桁も当選する仕様」なら理論値は上記2倍の0.7187%になるので理論値とかなり近しくなりますが、これはかなり不思議です。というのも、筆者が特等を引いた7回とも「下5桁」で当選しているからです

※7回続けて「下5桁」で当選するのは統計学的に有意水準1%で有意な偏り(符号検定よりp<0.01)と言えます。要するに偶然の範疇を超えています

なので、もしかしたら、ロトミは内部的に2回「下5桁完全一致」の抽選を行っているのかもしれませんし、上5桁で当選した時に、下5桁で言い直しているのかもしれません。

いずれにしても、実績値から考えるに当選確率は、単純な「下5桁完全一致」で計算した理論値の2倍と考えるのが妥当なので、ロトミIDくじの各賞品の当選確率は下表になるかもしれません。どちらを信じるかは読者の判断に委ねます。

 

所持ID数と各賞品の当選確率(特等の当選確率が2倍で仮定した時の試算)
ID数 【特等】5桁
マスターボール
【1等】4桁
ふしぎなあめ
【2等】3桁
ポイントマックス
【3等】2桁
ポイントアップ
【4等】1桁
モーモーミルク
はずれ
1 0.00% 0.01% 0.09% 0.90% 9.00% 90.00%
10 0.02% 0.06% 0.82% 7.75% 52.41% 38.94%
25 0.05% 0.15% 2.03% 18.00% 70.31% 9.46%
50 0.10% 0.30% 4.00% 31.96% 62.74% 0.90%
100 0.20% 0.60% 7.81% 50.90% 40.48% 0.01%
300 0.60% 1.77% 21.30% 69.69% 6.64% 0.00%
500 1.00% 2.93% 32.33% 62.66% 1.09% 0.00%
700 1.39% 4.06% 41.31% 53.07% 0.18% 0.00%
960 1.90% 5.49% 50.48% 42.11% 0.02% 0.00%
966 1.91% 5.52% 50.66% 41.89% 0.02% 0.00%

 

プログラムの使い方(ランクマバグの入り方)

おまたせしました。

肝心のプログラムの使い方ですが、ロトミの前でソースコードを書き込んだArduinoを挿し込むだけの簡単仕様です。ただし、いわゆる「ランクマバグ」状態で使う必要がありますので、ランクマッチで1戦してください。

なお、ランクマッチを使用しない方法での「ランクマバグ状態」移行は下記手順でできます。画像は過去記事の使いまわしです。あしからず。

  1. YY通信で「通信対戦」を行う(ローカル通信)
  2. もう一台のSwitch・剣盾を使ってその通信対戦に応じる
  3. 戦闘が始まったらHomeボタン長押し→「機内モード」をONに変更する
  4. エラーが発生するのでそれを閉じて、「にげる」選択
  5. フィールド画面に戻ってくる(ランクマバグ状態に移行完了)
f:id:tangential_star:20201122213022g:plain
f:id:tangential_star:20201122213037g:plain
YY通信でローカル対戦を募る。バトルが始まったらHomeボタン長押しで機内モード
f:id:tangential_star:20201122213148g:plain
f:id:tangential_star:20201122213205g:plain
エラーが出たら「にげる」で対戦終了。この状態で時刻変更すると一瞬画面が暗転する

 

ソースコード

今回も例のごとく、「ランクマバグ」状態で使います。使い方は、最寄りのポケモンセンターのロトミの前でArduinoを挿し込むだけの簡単仕様です。

30試行あたり19分47秒(実測値)だったので、ざっくり1時間放置で90回抽選できます。

NintendoSwitch Ver.11.0アップデート対応済です。

/* 
 *  ロトミIDくじ自動抽選c⌒っ.ω.)っ
 *  
 *  【ランクマバグ】状態で使うこと!
 *  ロトミの前でArduino差し込むだけ!
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h>

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



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

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


void loop() {

  // ★日付を回すやつ
  NextDayInCheatMode();

  PushKey("A", HOLDTIME, 700); // こんにちロ~! なにを しますロミ? ▼
  PushKey("A", HOLDTIME, 700); // 
  PushKey("down", HOLDTIME, 350); // IDくじ
  PushKey("A", HOLDTIME, 700); // ただいま IDくじセンターの 抽選コーナーに つないだロミ!▼
  PushKey("A", HOLDTIME, 700); // 引いた くじの ナンバーと ○○さんの ポケモンのIDが▼
  PushKey("A", HOLDTIME, 700); // みごと あってると ステキな 賞品を もらえちゃうんだロ!▼
  PushKey("A", HOLDTIME, 700); // 運試しに レポートを 書いて 引いてみるのは どうロミ?
  PushKey("A", HOLDTIME, 2000); // はい⇒レポートを書き込んでいます⇒しっかり書き残した▼
  PushKey("B", HOLDTIME, 800); // かしこまりロ~! 抽選 スタート ロミ!▼
  PushKey("B", HOLDTIME, 1200); // …… …… ……▼
  PushKey("B", HOLDTIME, 700); // ハイ! でたロミ! くじの ナンバーは *****!▼
  PushKey("B", HOLDTIME, 700); // ○○さんの ポケモンのIDと どれだけ あってるか▼
  PushKey("B", HOLDTIME, 700); // 調べて 見るロ!▼
  PushKey("B", HOLDTIME, 3000); // おめでロ~~~!!!!▼
  PushKey("B", HOLDTIME, 700); // ボックスに 預けている ○○ちゃんの IDが みごと▼
  PushKey("B", HOLDTIME, 700); // くじの ナンバーと ぴったしロミ!▼
  PushKey("B", HOLDTIME, 700); // ロミ! ○ケタが おんなじロ!▼
  PushKey("B", HOLDTIME, 700); // そんな スペシャルな 奇跡には 2等の 賞品▼
  PushKey("B", HOLDTIME, 700); // ポイントマックスを プレゼントだロ!!▼
  
  PushKey("B", HOLDTIME, 3000); // ○○は ポイントマックスを 手に入れた!▼
  PushKey("B", HOLDTIME, 700); // ○○は ポイントマックスを どうぐポケットに しまった▼
  PushKey("B", HOLDTIME, 900); // それじゃあ またの 挑戦を お待ちしてるロ~~!▼
  PushKey("B", HOLDTIME, 700); // ▼
  
  PushKey("B", HOLDTIME, 300); // 予備
  PushKey("B", HOLDTIME, 300);
  PushKey("B", HOLDTIME, 300);
  

}

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;
}

 

あとがき

今回は、「ロトミIDくじ」の自動化について紹介しました。

ロトミってRotom Information Serviceの意味だったんですね。この記事を書くまで知りませんでした。

 

さて、本稿でも結構考察しましたが、様々なWebサイトを調べてもマスターボールの当選について「5桁一致」とは書いてあるものの「下5桁」のみならず「上5桁」も含むのかが分からず、自分で検証してしまいました。

結論、ロトミは「下5桁」でしか当選を教えてくれないが、確率は単純計算時の2倍で設定されている、という仮説ができましたね。そういう意味では、ある意味「自動化記事」でありながら「疑問だし記事」にもなってしまいました(笑)。

誰か、数学に強い人か、ロトミに詳しいポケモン博士にぜひともコメントしていただきたいものです(もしかしたらそもそも私の試算が間違っている、なんて本末転倒かもしれませんし)。

 

話が逸れましたが、これで「マスターボール」の自動入手もできるようになりました。マスターボールは「孵化厳選」では絶対に引き継げない、とても希少なボールですし、ボールエフェクトもカッコイイんですよね。そういう意味でも、すごく需要があるボールなのに…。もう少し手軽に入手できれば良いのにな、と思います。そういった意味でも、需要のある自動化だったのではないでしょうか。

 

本ブログが皆様の自動化ライフをより豊かなものにできれば幸いです。

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

 

前記事:【Arduino自動化13】全自動ポケモン逃がし

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

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

 

2021/2/19追記:IDを効率よく集められる「ミラクル交換」の自動化記事を執筆しました。

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