mizdra's blog

ぽよぐらみんぐ

乱数調整 入門

追記 (2016/12/29)

誤りの訂正, その他細かな修正を行いました. 差分は以下のページから確認できます.


5日間もの大遅刻をしてしまって申し訳ないです 🙇


はじめに

この記事はPokémon RNG Advent Calendar 2016 一日目の記事です🎉🎉🎉

www.adventar.org

この記事では主にコンピュータゲームで話題になっている乱数調整について, ポケモンを例に説明します. 以下の方々を対象にした入門的な記事になっています.

  • 乱数調整という言葉を初めて聞いた人
  • 乱数調整という言葉を聞いたことはあるが, その意味や具体的な手順を知らない人
  • 乱数調整をやってみたいと思っているが, 手を出せずにいる人

ポケモンのゲームシステム, 16進数表記, ビット演算を知っている/理解できる前提で書いています. ご了承下さい.

わかりやすさを優先して一部現実と異なる事が書かれている場合があります. 誤字や重大な間違いを発見した場合は修正しますのでこの記事のコメントもしくはTwitterのリプライにてお気軽にご連絡下さい.

「乱数調整」とは

記事のタイトルを見て, 「乱数調整って何だろう?」と疑問に思った方もいるでしょう. 本記事では「乱数調整」の仕組みを理解し, 実際に乱数調整を行ってみることを想定したものとなっています. 「乱数調整」について説明する前に「乱数」について軽く学習しましょう.

「乱数」とは

「乱数」とはサイコロの出目のように規則性がなく予測不能な数のことです. サイコロを例にとって考えてみます. サイコロを数回振ると, 1, 3, 4, 3, 2, 6, 1, ... というように1~6の出目を得ることができます. 一般に, サイコロの出目には規則性が無く, 得られる値はでたらめに並んでいます. このように, でたらめに並んだ数列を「乱数列」と言い, その数列の元を「乱数」と言います*1. 乱数列はそれ以前の数から次の数を予測することが出来ないという特徴があります.

1回目 2回目 3回目 4回目 5回目 6回目
2 4 6 4 4 1
7回目 8回目 9回目 10回目 11回目 12回目
3 4 5 5 6 ?

※ サイコロを11回振った時の出目を表にしました*2. 1 ~ 11回目の出目に規則性が無く, 12回目の出目を予測することは出来ません.

乱数の種類

ここで, コンピュータで乱数を扱う方法を紹介します. 詳しい説明は省きますが *3, コンピュータは確定的な計算を用いてしか乱数を生成することが出来ません. 以下にコンピュータ上での具体的な乱数の計算例を示します.

S[0] = 0
S[n+1] = (3 * S[n] + 5) % 7
S = { 5, 6, 2, 4, 3, 0, 5, 6, 2, 4, 3, 0, 5, 6, 2, 4, 3, 0, 5, 6, ... }

この乱数生成法は「線形合同法(LCG)」と呼ばれ, 漸化式を用いた簡易的な乱数生成法の1つです*4.

線形合同法は適当な初期値からスタートして、次の漸化式で乱数を生成します。


X[i] = (A * X[i-1] + C) mod M


これで 0 以上 M 未満の値を生成することができます。A, C, M の選び方は 参考文献 [2] より引用します。


M が 2 の累乗なら A mod 8 を 5 または 3 とし(5 の方が安全)、定数項 C を奇数とする。このとき、周期はちょうど M となり、その 1 周期分には 0 から M - 1 までの整数が 1 個ずつ現れる。


高速にするには C = 0 とし、初期値 X0 を奇数にする。ただし、周期は M / 4 になる。


Algorithms with Python / 乱数

S[0] は乱数列の初項で, 初期Seed, もしくはSeedと呼ばれます. 乱数列は初期Seedと漸化式を用いて逐次的に求められます. このように確定的な計算によって得た乱数を擬似乱数*5, 対してサイコロのような確定的な計算以外で得た乱数を自然乱数と区別して呼びます. 擬似乱数生成法には漸化式以外を用いたものもありますが, 漸化式を用いた擬似乱数生成法には以下のようなメリットがあり, コンピュータと相性が良いのでこの方法が主流です.

  • 漸化式と初期Seedを記録しておけば、誰でも同じ数列を再現できる (注: ある乱数列を再現するために全ての項を記録しておく必要がない)
  • (コンピュータ上での計算が)高速で低コスト


あなたの使っている乱数、大丈夫?-危ない標準乱数と、メルセンヌ・ツイスター開発秘話-

初期Seedは乱数列を決定する重要な要素の1つで, 出来るだけ乱雑な値になるように設定されます. 通常は乱数生成器を起動した時刻などを元に設定されます.

擬似乱数列は確定的な計算によって生成されているので, それを構成する各乱数の値を算定することが可能です. 事前に擬似乱数列を構成する個々の乱数を求めることができれば, それらを基に決定される事象を予測し, 狙った事象を発生させることも可能となります.

改めて「乱数調整」とは

乱数を軽く理解できたところで改めて乱数調整の意味について説明します. 乱数調整とは「 次に得られる乱数を予測し, 狙った乱数を得ること 」を指します. 先程のサイコロの例で考えるならば, 何かしらの手法で12回目のサイコロの出目を事前に予測し, 実際に予測した出目を得ること, これが乱数調整です.

実際にはサイコロの出目のような自然乱数を予測することは神でもない限り出来ないので, 算定可能な擬似乱数を対象に乱数調整が行われます.

乱数調整は「 事象を予測し, 狙った事象を発生させること 」といったようにもっと広義的に捉えられる場合もあります*6. これは調整する対象を乱数から事象(将来発生する事象)に拡張しただけです. これは調整する対象をミクロな見方をするか/マクロな見方をするかの違いです. この記事ではポケモンにおける乱数調整で一般に利用されている後者の意味で乱数調整を捉えます.

ポケモンで使用される乱数生成法

乱数調整の言葉の理解が深まったところでポケモンでどのように乱数が扱われているかを見ていきましょう!

使用するハードウェア, ソフトごとに乱数生成法が異なりますが, 今回は DS Lite + ポケットモンスター ハートゴールド(HG)を使用します. ポケモンシリーズで使用される乱数生成法の多くはネットのえらい人たちが既に調べてくれているのでわざわざ自分で調査する必要はありません. えらい人たちに感謝 🙏

HGでは以下で定義される線形合同法によって乱数が生成されています.

frame  := フレーム (0 < frame)
year   := 年 (2000 ≦ year ≦ 2099)
month  := 月 (1 ≦ month ≦ 12)
day    :=  日 (1 ≦ day ≦ 31)
hour   := 時間 (0 ≦ hour ≦ 23)
minute := 分 (0 ≦ minute ≦ 59)
second := 秒 (0 ≦ second ≦ 59)

S[0]   = (((month * day + minute + second) * 0x1000000)
         + (hour * 0x10000)
         + (frame + year - 2000)) % 0x100000000

S[n+1] = (0x41C64E6D * S[n] + 0x6073) % 0x100000000

年, 月, 時間, 分などの日時はゲームで「つづきからはじめる」を押した瞬間のハードに設定されている日時が用いられ, frame はDSを起動してソフトを選択し, ゲームが起動した瞬間から「つづきからはじめる」を押した瞬間までの時間をフレーム*7(単位の記号はF, 1秒≒60F)という単位に換算したものです. ただし実際には, DSのメニューからソフトを選択し, 起動するまでに350F(約5.8秒)のラグが発生 *8 しており, その時間は frame に含まれないことに注意して下さい. また, ゲームの仕様によりフレームは2ずつしか増加しません.

f:id:mizdra:20161206214959j:plain

例えば, 僕が今この文章を書いている2016年11月26日3時10分10秒*9にDSのメニューからゲームを選択し, 2016年11月26日3時10分40秒に「つづきからはじめる」を押したとすると, 初期Seedは以下のようになります .

frame  = (40s - 10s) * 60F - 350F = 1450
year   = 2016
month  = 11
day    = 26
hour   = 3
minute = 10
second = 40

S_0    = (((month * day + minute + second) * 0x1000000)
         + (hour * 0x10000)
         + (frame + year - 2000)) % 0x100000000
       = 0x500305BA

擬似乱数

LCGで生成される乱数にはとても不思議な特徴があります. ここでLCGの漸化式を見てみましょう.

S[n+1] = (0x41C64E6D * S[n] + 0x6073) % 0x100000000

0x41C64E6D, 0x6073 は両方とも奇数であることに注目して下さい.

  • 奇数*偶数+奇数 は奇数
  • 奇数*奇数+奇数 は偶数

より, なんとこの擬似乱数列は 偶数, 奇数, 偶数, 奇数, ... を繰り返してしまいます! 不思議ですね! これはよく知られたLCGの欠点の1つで, 最下位bitが 0, 1 を繰り返すというものです.

UNIX の /usr/ucb/cc を解析した結果、 rand( ) は以下の動作をすることがわかった。


static long x=1;

void srand(long s) { x=s; }

long rand() { x=x*1103515245+12345; return x&2147483647; }


これは、非常にシンプルな線形合同法である。 この乱数の最下位ビットは0と1の繰り返しになる。 すなわち、偶数と奇数が交互に生成される。 このことから、この乱数で下位ビットを乱数として使うのは危険であることがわかる。 また、ある乱数が得られたら、次に現れる乱数が1種類しかないという欠点も持つ。


良い乱数・悪い乱数

これを知らずにLCGで生成された生の乱数を使用すると大きな偏りが生じてしまうので *10 , ポケモンでは S[n] >>> 16 (>>> は0 埋め右シフト) を擬似乱数として使用しています. 本記事では説明のために実擬似乱数r[n] = S[n] >>> 16 と定義しておきます (様々な乱数調整関連の記事で r[n] という式自体は出てきますが, それに名前が付いてなくて不便なので勝手に僕が名付けました).

乱数の消費

ポケモンでは新しい乱数が生成されたとき, 「乱数が消費された」と言い, 乱数生成器で乱数が生成される度に乱数が消費されていきます. 乱数が2度生成された場合は2回乱数が消費され, 「乱数が2消費された」「2F消費された*11」といった表現が使われます.

以下に乱数を消費する事象の代表的な例を示します.

消費数 事象
1 ウツギ博士に電話を掛ける(通話内容の決定で乱数が消費される)
1 NPCが振り向く/移動する*12 (NPCが振り向く向き, 静止時間の決定)
1 録音した音声を鳴き声として設定したペラップの鳴き声を聞く(鳴き声を聞く度に音の高さが変わる, 音の高さの決定で乱数が消費される)
2 たんパンこぞう ゴロウに電話を掛ける(電話内容自体に疑似ランダムな要素はありませんが, 内部で乱数生成器によって2F消費されています)
4 連れ歩いている眠り状態のポケモンに話しかける(上と同じく疑似ランダムな要素はありませんが, 内部で乱数生成器によって4F消費されています)
徘徊ポケモンの数 以上 「つづきからはじめる」を押してゲームを再開する(徘徊ポケモン*13の出現ポイント決定に乱数が消費される, ゲーム再開前後で徘徊ポケモンの出現ポイントが同じになったとき出現ポイントの再決定が行われる, ゲーム再開時に自動的に消費が行われるので強制消費と呼ばれます)
手持ちポケモンの数 128歩歩く(128毎歩くたびに行われる手持ちポケモンのなつき度判定で乱数が消費される)
12以上 ラジオのチャンネルをオーキドはかせのポケモンこうざに切り替える(詳細: オーキドはかせのポケモンこうざ - oupoの日記)

これらの乱数消費法を用いることで, 自分の好きな回数だけ乱数を消費できます.

ウツギ博士との電話で乱数調整

「消費」という概念, 上の説明だけでは分かりにくいですね... 実際にウツギ博士に電話を掛けて消費がどんな操作であるのかを確認しましょう.

準備

ウツギ博士が電話で話す内容はストーリーの進行具合によって変わってくるのですが, 今回は以下の条件の元でやってみます.

  • 殿堂入り前にウツギ博士にトゲピーを見せ, 変わらずの石を貰っている
  • 殿堂入りをしている

この条件下では, ウツギ博士が電話で話す内容が3種類に固定され, 電話をする度に r[n] % 3 で対応するメッセージが決定されるようになります.

r[n] % 3 メッセージ(先頭のみ)
0 ポケモンの しんかというのは~
1 カントーには まだ ぼくの〜
2 ポケルスが くっついた~

今回は先程計算した初期Seed 0x500305BA で乱数調整を行います. 初期Seed 0x500305BA での n = 20 までの乱数列は以下のようになります.

n S[n] r[n] r[n] % 3 メッセージ(先頭のみ)
0 0x500305BA 0x5003 2 ポケルスが くっついた~
1 0xA4E47CA5 0xA4E4 2 ポケルスが くっついた~
2 0x1FE1B8B4 0x1FE1 1 カントーには まだ ぼくの〜
3 0xE89ADD17 0xE89A 2 ポケルスが くっついた~
4 0x1017853E 0x1017 0 ポケモンの しんかというのは~
5 0xD090FFD9 0xD090 1 カントーには まだ ぼくの〜
6 0xF6876DD8 0xF687 0 ポケモンの しんかというのは~
7 0x0831F56B 0x0831 0 ポケモンの しんかというのは~
8 0xB1CE7902 0xB1CE 2 ポケルスが くっついた~
9 0xB754824D 0xB754 0 ポケモンの しんかというのは~
10 0x233D513C 0x233D 0 ポケモンの しんかというのは~
11 0xC0443EFF 0xC044 2 ポケルスが くっついた~
12 0x207AE506 0x207A 1 カントーには まだ ぼくの〜
13 0xEEBFB801 0xEEBF 0 ポケモンの しんかというのは~
14 0xA37806E0 0xA378 1 カントーには まだ ぼくの〜
15 0x5D738DD3 0x5D73 1 カントーには まだ ぼくの〜
16 0x439C0D4A 0x439C 1 カントーには まだ ぼくの〜
17 0x66BA94F5 0x66BA 0 ポケモンの しんかというのは~
18 0x015272C4 0x0152 2 ポケルスが くっついた~
19 0x36AAF5E7 0x36AA 2 ポケルスが くっついた~
20 0x356175CE 0x3561 0 ポケモンの しんかというのは~

ポケモンでは S[0] は疑似ランダムな事象を決定するためには用いられず, S[1] 以降が用いられます. つまり, ゲームを再開して初めて生成される乱数は S[1] となります. また, 僕が乱数調整を行うHGは徘徊ポケモンが3匹いるので, ゲーム再開時に自動的に3~6F消費されます. よって初めにウツギ博士に電話を掛けるときに使われる乱数は S[4], S[5], S[6] のどれかです.

乱数調整する際は出来るだけ不定NPC (先程の消費の例で挙げた乱数を消費するNPCのこと) がいない所でやると良いでしょう. 自分が消費している最中にNPCに勝手に消費されてしまっても困りますからね. 不定NPCがいない所であればどこでも良いです. 僕は主人公の部屋でやることにしました. ここで一度セーブしてハードの電源を切りましょう.

f:id:mizdra:20161204182703j:plain

トロフィーコンプしてないことがバレてしまう…

初期Seedを合わせる

初期Seed 0x5003048F に合わせるには2016年11月26日3時10分10秒にDSのメニューからゲームを選択し, 2016年11月26日3時10分40秒に「つづきからはじめる」を押す, でしたね. 以下の手順で初期Seedを合わせましょう.

  1. エメタイマーで30秒間のタイマーをセットする
  2. ハードの電源を入れ, 本体設定パネル > 日付&時刻パネル > 日付パネル 及び 本体設定パネル > 日付&時刻パネル > 時刻パネル からハードの日時・時刻を2016年11月26日3時10分0秒に設定する
  3. 直ぐにハードを再起動し, 上画面の時計の針が10秒を指した直後にHGを選択する. 同時にエメタイマーを起動する.
  4. 「つづきからはじめる」ボタンの画面で待機し, エメタイマーのカウントが0になったらボタンを押す
  5. ゲームが再開したらポケギアのマップを開き, 徘徊ポケモンの位置から初期Seedを特定する
  6. 初期Seedが目標のものと異なっていたらずれを確認し, タイマーにずれを反映させて 2 に戻る. 一致すればそこで終了.

「エメタイマー」, 「徘徊ポケモンの位置」のあたりを解説します.

1. エメタイマーで30秒間のタイマーをセットする

ポケモンでは frame を合わせるためにエメタイマーと呼ばれるタイマーをよく使います *14. これは乱数調整用に開発されたタイマーでフレーム⇛秒変換機能など乱数調整を補助する様々な機能が付いています.

使い方はサイトの下部にある説明を読めばわかります. 今回は30秒間待機したいので「時間」欄に「30」と入力しました.

f:id:mizdra:20161204190438p:plain

5. ゲームが再開したらポケギアのマップを開き, 徘徊ポケモンの位置から初期Seedを特定する

「乱数の消費」の項でも取り上げましたが, 徘徊ポケモンの位置の決定には S[1] 以降の任意の個数の乱数が使われます (徘徊ポケモンが3匹の場合). これを利用して徘徊ポケモンの位置からどの初期Seedを引いたのかある程度絞ることが出来ます. 「HGSS徘徊 初期seed確認」というツールを使ってみましょう.

セーブ直前の位置

セーブした直前での徘徊ポケモンの位置を <ライコウの位置>,<エンテイの位置>,<ラティアスの位置> の形式で入力します. 位置は 29ばんどうろ だったら 29, 21ばんすいどう だったら 21 といったように数値で指定します. 今回は 32ばんどうろ,29ばんどうろ,5ばんどうろ だったので 32,29,5 と入力しました.

ロード後の位置

ソフトを再開したときの徘徊ポケモンの位置を入力します. 形式は「セーブ直前の位置」欄と同じです.

目標初期seed

合わせたい初期Seedを入力します. 今回は 0x500305BA と入力しました.

下4桁の範囲

徘徊ポケモンの位置を検索するの初期Seedの範囲を, 初期Seedの下4桁の範囲で決めます. 「つづきからはじめる」を押した日時・時刻が固定であれば初期Seedの上位4桁は固定なので, 基本的に下4桁のみが変動します. 今回は 0x500 ~ 0x5D0 と入力しました.


実際にこの手順でやると, ゲーム再開後の徘徊ポケモンの位置は 29,45,3 となりました. これを先程の「ロード後の位置」に入力して「計算」ボタンを押すと以下の出力が得られました.

f:id:mizdra:20161204203327p:plain

これより, 目標の初期Seed 0x500305BA に対して 0x500305A0 を引いていたことがわかります. 下4桁の差を取ると 0x05BA - 0x05A0 = 26 となり, 待機時間が 26F 短かったことになります. ちなみに括弧の中に表示されている数はその初期Seedでの強制消費数を表しています. つまり, 目標の初期Seed 0x500305BA での強制消費数は3F, 初めにウツギ博士に電話を掛けるときに使われる乱数は S[4] となります.

次に待機時間のずれをタイマー側にも反映させましょう. 30秒間の待機時間を 26F だけ延長したいので 30s * 60 + 26F = 1826F1826F を待機時間として設定すれば良いことになります.

f:id:mizdra:20161204204323p:plain

この設定でもう一度 2~5 の操作をやってみたところ, 見事徘徊ポケモンの位置が 33,30,22 となり, 初期Seedを 0x500305BA に合わせることに成功しました!

めでたい! 🎉🎉🎉

f:id:mizdra:20161204210441j:plain

今回は2回目のチャレンジで初期Seedを合わせることに成功しましたが, 1/60秒間隔での操作が要求されるので割りとシビアです. いつかは成功するのでめげずに何度も挑戦してみましょう.

ここまでの操作をおさらいします.

  1. エメタイマーで30秒間のタイマーをセットする
  2. ハードの電源を入れ, 本体設定パネル > 日付&時刻パネル > 日付パネル 及び 本体設定パネル > 日付&時刻パネル > 時刻パネル からハードの日時・時刻を2016年11月26日3時10分0秒に設定する
  3. 直ぐにハードを再起動し, 上画面の時計の針が10秒を指した直後にHGを選択する. 同時にエメタイマーを起動する.
  4. 「つづきからはじめる」ボタンの画面で待機し, エメタイマーのカウントが0になったらボタンを押す
  5. ゲームが再開したらポケギアのマップを開き, 徘徊ポケモンの位置から初期Seedを特定する
  6. 初期Seedが目標のものと異なっていたらずれを確認し, タイマーにずれを反映させて 2 に戻る. 一致すればそこで終了.

乱数を消費する

初期Seedを合わせ終わったので後は乱数を消費するだけです. 17回電話を掛けた場合 *15, 上で計算した乱数列から次の順番でメッセージが流れるはずです.

n S[n] r[n] r[n] % 3 メッセージ(先頭のみ)
4 0x1017853E 0x1017 0 ポケモンの しんかというのは~
5 0xD090FFD9 0xD090 1 カントーには まだ ぼくの〜
6 0xF6876DD8 0xF687 0 ポケモンの しんかというのは~
7 0x0831F56B 0x0831 0 ポケモンの しんかというのは~
8 0xB1CE7902 0xB1CE 2 ポケルスが くっついた~
9 0xB754824D 0xB754 0 ポケモンの しんかというのは~
10 0x233D513C 0x233D 0 ポケモンの しんかというのは~
11 0xC0443EFF 0xC044 2 ポケルスが くっついた~
12 0x207AE506 0x207A 1 カントーには まだ ぼくの〜
13 0xEEBFB801 0xEEBF 0 ポケモンの しんかというのは~
14 0xA37806E0 0xA378 1 カントーには まだ ぼくの〜
15 0x5D738DD3 0x5D73 1 カントーには まだ ぼくの〜
16 0x439C0D4A 0x439C 1 カントーには まだ ぼくの〜
17 0x66BA94F5 0x66BA 0 ポケモンの しんかというのは~
18 0x015272C4 0x0152 2 ポケルスが くっついた~
19 0x36AAF5E7 0x36AA 2 ポケルスが くっついた~
20 0x356175CE 0x3561 0 ポケモンの しんかというのは~

動画を取りました.


ウツギ博士乱数

確かに上の表の通りにメッセージが流れています. 乱数調整成功です. 👏👏

ウツギ博士との電話で乱数調整 その2

ただ予想通りにメッセージが流れただけではつまらないのでもう少し高度なことをしてみましょう. 今度は,

  • ウツギ博士が「ポケモンの しんかというのは~」というメッセージを15回以上連続で話す

ことを乱数調整してみます. ウツギ博士に進化について熱く語ってもらいましょう.

乱数と初期Seedの計算

まず目的のメッセージを15回以上連続で話す乱数 S[n], 及びその初期Seedを計算しなければなりませんね. 本来であれば既存のツールを使って計算…といきたいところですかこんなどうでもいい乱数調整のためのツールなんかあるはずがありません. ではどうするか. 無いなら作ってしまいば良いのですよ. ✨😉☝️

という訳でサクッとC++で目的の乱数と初期Seed, 必要な消費数を出力するプログラムを書きました. プログラミング チョットデキルな皆さんなら当然書けますよね???

ウツギ博士にポケモンの進化について熱く語ってもらう.

# ソースコードをclone
$ git clone https://gist.github.com/faf3c48f67d999fc8072795e6c67a901.git abuse-dr-utsugi

# コンパイル
$ cd abuse-dr-utsugi
$ g++ AbuseDrUtsugi.cpp -o AbuseDrUtsugi.exe -std=c++11

# 実行
$ ./AbuseDrUtsugi.exe
  n        S[n]        S[0]
 10  0X23E5E254  0XD2160812
  5  0X2A991401  0X89070502
  9  0X3B4F3C64  0X96010A6D
  4  0X72038E62  0X4114049E
  5  0X7B215E11  0XE8040852
  6  0X810C32E0  0X89070502
  5  0XA2B1DC2D  0X4114049E

いくつか初期Seedの候補か出力されましたが, ここでは初期Seed 0x89070502 を狙います. 先程と同様に手計算で起動時刻を求めました.

  • DSメニューからゲームを選択: 2099年5月20日7時2分10秒
  • 待機時間: 1533F
  • 「つづきからはじめる」を選択: 2099年5月20日7時2分35秒

初期Seedを合わせる

エメタイマーは以下のように設定しておきます.

f:id:mizdra:20161205191833p:plain

僕の場合はセーブ直前での徘徊ポケモンの位置が 32,29,5 だったので「HGSS徘徊 初期seed確認」には以下のように設定しました.

f:id:mizdra:20161205193404p:plain

1回目

徘徊ポケモンの位置は 30,31,12 でした.

f:id:mizdra:20161205194342p:plain

0x502 - 0x4ee = 20 より, 待機時間を 20F 延長し, エメタイマーに 1553F を設定しました.

f:id:mizdra:20161205193712p:plain

2回目

徘徊ポケモンの位置は 30,32,9 でした.

f:id:mizdra:20161205194006p:plain

おや…? 徘徊ポケモンの位置を正しく入力したのに初期Seed候補が出力されませんね. ここで左下にある「秒のずれを許容」にチェックを入れて秒や分のずれを考慮した初期Seed候補検索をしてみましょう.

f:id:mizdra:20161205194559p:plain

初期Seed候補 0x8a070500 が出力されました. ここで, 0x8a - 0x89 = 1 より, 「つづきからはじめる」を押した時刻が1秒遅れたと推測されます. エメタイマーの設定はそのままにして, DSメニューからゲームを選択する時刻を1秒早めて(2099年5月20日7時2分9秒)みます.

3回目

徘徊ポケモンの位置は 29,35,2 でした. 成功です 👍

乱数を消費する

強制消費数は3Fなので, 初めにウツギ博士に電話を掛けるときに使われる乱数は S[4] となります. 乱数列を計算すると, 以下のようになっています.

n S[n] r[n] r[n] % 3 メッセージ(先頭のみ)
0 0x89070502 0x8907 0 ポケモンの しんかというのは~
1 0xDD101E4D 0xDD10 0 ポケモンの しんかというのは~
2 0x05A6BD3C 0x05A6 0 ポケモンの しんかというのは~
3 0xCE0F3AFF 0xCE0F 2 ポケルスが くっついた~
4 0xC0B03106 0xC0B0 2 ポケルスが くっついた~
5 0x2A991401 0x2A99 0 ポケモンの しんかというのは~
6 0x810C32E0 0x810C 0 ポケモンの しんかというのは~
7 0xE2F249D3 0xE2F2 0 ポケモンの しんかというのは~
8 0x1FDA194A 0x1FDA 0 ポケモンの しんかというのは~
9 0x5CCDB0F5 0x5CCD 0 ポケモンの しんかというのは~
10 0x41FD5EC4 0x41FD 0 ポケモンの しんかというのは~
11 0x5B5971E7 0x5B59 0 ポケモンの しんかというのは~
12 0xE57441CE 0xE574 0 ポケモンの しんかというのは~
13 0x51E12929 0x51E1 0 ポケモンの しんかというのは~
14 0xB61F64E8 0xB61F 0 ポケモンの しんかというのは~
15 0x0F8D073B 0x0F8D 0 ポケモンの しんかというのは~
16 0x29E26E92 0x29E2 0 ポケモンの しんかというのは~
17 0x6A05F09D 0x6A05 0 ポケモンの しんかというのは~
18 0xEA45A94C 0xEA45 0 ポケモンの しんかというのは~
19 0x36069DCF 0x3606 0 ポケモンの しんかというのは~
20 0xA200A396 0xA200 0 ポケモンの しんかというのは~
21 0xC821BB51 0xC821 2 ポケルスが くっついた~

よって16連続でウツギ博士がポケモンの進化についての話をしてくれます. 熱いですね.

動画を取りました.


ウツギ博士乱数 (16連続同一メッセージ)

確かに16連続で熱い話をしてくれていますね. 乱数調整成功です 👏👏

この記事で行う乱数調整はこれで終わりです *16. 乱数列を意識しながら乱数調整を行ったので, 疑似ランダムな事象の裏ではどんな計算が行われているのかということをイメージする力が付いたのではないでしょうか. 以下で他にどんなことに対して乱数調整できるのか軽くリストアップしたので是非ご覧になって下さい (色乱数や高個体値ポケモンの乱数はありきたりすぎるのでカットで 👋).

他にどんなことが乱数調整できるの?

面倒になってきたので雑にいきます.

d.hatena.ne.jp

僕がパッと思いついたのはこれくらいですが, これは入れるべきというものがあれば教えてください 🙏

乱数調整の勉強する際に参考になるサイト

上に同じく, これは入れるべきというものがあれば教えてください 🙏🙏🙏

おわりに

以上が Pokémon RNG Advent Calendar 2016 1日目「乱数調整 入門」となります. ここまで読んでくださった方々, ありがとうございます! お疲れ様でした! 😃

期限までに書ききることができなかったのは残念 *17 ですが, Pokémon RNG Advent Calendar 2016 では興味深い記事が次々と投稿されてきています. 残りの日も Pokémon RNG Advent Calendar 2016 で楽しんでいきましょう!

www.adventar.org

2日目は @sub_827 さんの担当です!

*1:つまり, 「乱数」とは規則性がなく予測不能な数のことです.

*2:家からサイコロ探し出して1人で転がしてました

*3:僕はこのあたりのことについて詳しくないので何も言えません

*4:他にもメルセンヌ・ツイスタ法やカオス乱数などがあります. (参考: 基礎知識「乱数(ランダムナンバー)の種類と生成方法」-【ケータイキャンペーン屋さん】)

*5:後ほど述べますが, 擬似乱数は算定可能なので予測不能な数では無い, 乱数ではないとも言われています

*6:調整するのは「乱数」であり「事象」ではないので厳密には間違っています

*7:Frameの原意は動画を構成する一枚一枚の静止画(コマ)のことで, ポケモンにおける乱数調整では経過時間を表す単位として用いられます

*8:参考: 雪の舞う夜に。 : 【乱数調整】空白時間の測定【HGSS・DPPt】

*9:何でこんな時間に記事を書いているんでしょうね...

*10:うっかり「乱数ではない乱数のようなもの」を使わないよう, 乱数を扱うときはその乱数生成法の利点と欠点についてよく調べておきましょう

*11:描画処理1Fにつき乱数が1消費されることから、消費される乱数の個数を数える際にFを単位として用いる慣習があります

*12:一定時間ごとに決まった向きに振り向くなど周期的な動作をするNPCは乱数を消費しません

*13:"徘徊系ポケモン(はいかいけい-)とは、出現ポイントがある条件によってマップ上を移動する伝説のポケモンのこと。" http://wiki.xn--rckteqa2e.com/wiki/%E5%BE%98%E5%BE%8A%E7%B3%BB%E3%83%9D%E3%82%B1%E3%83%A2%E3%83%B3

*14:Flash製のツールです. 時代を感じますね…

*15:17回も連続で電話をかけるだなんて主人公も迷惑な野郎ですね

*16:撮影しながらの乱数調整しんどかった…

*17:今日 (記事公開日) は12/6 です. 大遅刻してしまって本当に申し訳ありません 🙇