裏CHUNSOFT > ROMイメージ改造・解析 > 乱数生成

乱数生成

コンピュータにおける乱数について

実はコンピュータが発生させる乱数は、本当の意味でランダムなのではなく、一定の初期値(これを乱数の種と呼びます)と決められたアルゴリズムに従って一見ランダムに見える数列を生成しているに過ぎないのです(このようにして発生させた乱数のことを擬似乱数と言います)。 コンピュータは決められた計算をすることはできても、ランダムな数字を発生させることはできないのです。

コンピュータでは、乱数は擬似乱数しか発生させることができないのですから、風来のシレンは勿論全てのゲームにおける乱数は擬似乱数です。 ただ不思議のダンジョンシリーズは、必要に応じて擬似乱数を発生させているのが特徴で、擬似乱数の実感がしやすいゲームだと言えます。 他のゲームでは、1フレーム毎に擬似乱数を発生していたりするのでなかなか実感ができません。

そのフロアにおけるシレンの行動を記録しておけば、後は乱数の種から正しくフロア生成や再開・回想ができたり、他の不思議のダンジョンシリーズで救助する場合に倒れた人と同じ地形を進むことになったり、Windows版風来のシレンのゴミ壺ダンジョンで何度プレイしても同じダンジョンになったりすることから、擬似乱数というものが実感できます。 また、保存しておいたセーブデータから再開し、そこから同じ行動をすることを繰り返すと何度やっても同じ結果になります。

乱数生成アルゴリズム

(擬似)乱数生成ルーチンは03F65Fh以降にあります。 以下はその逆アセンブルリストです。

数値は全て16進数です。 $XXはメモリ(変数)を表します。 乱数生成に影響しない命令にはコメントを付けていません。 1つ1つ解説するまでもない命令は、複数の命令をまとめて1つのコメントで解説しています。

03F65F  08           PHP             ; 
03F660  C2 20        REP #$20        ; 
03F662  A5 B7        LDA $B7         ; "A" = 100 * $B8 + $B7
03F664  0A           ASL * 5         ; "A" *= 20 (を10000で割った余り)
03F669  45 B8        EOR $B8         ; "A" = "A" xor 100 * $B9 + $B8
03F66B  0A           ASL             ; "A" *= 2 (を10000で割った余り)
03F66C  E2 20        SEP #$20        ; 
03F66E  A5 B8        LDA $B8         ; 
03F670  85 B9        STA $B9         ; $B9 = $B8
03F672  A5 B7        LDA $B7         ; 
03F674  85 B8        STA $B8         ; $B8 = $B7
03F676  A5 B6        LDA $B6         ; 
03F678  85 B7        STA $B7         ; $B7 = $B6
03F67A  EB           XBA             ; 
03F67B  85 B6        STA $B6         ; $B6 = "A" / 100 (余りは切り捨て)
03F67D  85 00        STA $00         ; $00 = "A" / 100 (余りは切り捨て) (これが新しい乱数になり、乱数生成ルーチンを呼び出した側で受け取られる)
03F67F  64 01        STZ $01         ; 
03F681  28           PLP             ; 
03F682  6B           RTL             ; 

$B6,$B7,$B8,$B9には今まで生成された乱数が入っています(例:05,DD,E8,F7)。

簡単に言うと、$B7のbit2〜$B8のbit1と、$B8のbit7〜$B9のbit6の排他的論理和が新しい乱数です。 後は、$B7〜$B9に$B6〜$B8の値を、$B6に新しい乱数を代入して終了です。

乱数は偏るか

不思議のダンジョンシリーズでは、よく「偏る乱数」が言われます。 そしてそれは、偏りやすい乱数生成アルゴリズムを使っているからだと言われることがあります。 少なくともSFC版風来のシレンでは、乱数生成アルゴリズムは上の通りですが、このようなアルゴリズムによって生成された数列が偏りやすい性質を持つのかは、私には分かりません(詳しい方がいましたら教えて下さい)。 そこで、ここでは統計で「偏る乱数」説を検証してみます。

JavaScriptを使って、同じアルゴリズムで乱数を16777216回発生させてみました($B6〜$B9の初期値は上の例のものを用いました)。 以下は0〜255の各値が出た回数です。

65424,65593,65649,65342,65422,65800,65473,65695,65402,65507,65717,65914,65332,65892,65493,65227,
65473,65759,65834,65637,65425,65533,65198,65165,65621,65786,65647,65445,65503,65906,65974,65379,
65556,65437,66012,65772,65402,65272,65664,65591,65411,65373,65333,65559,65967,65637,65172,65460,
65154,66120,65438,65406,64894,65559,65593,65948,65556,65381,65380,65333,65843,65582,65412,65488,
65550,65238,65509,65677,65347,65248,65311,65627,65645,65431,65757,65780,65811,65486,65480,65162,
65443,65369,65395,65388,65638,66025,65910,65218,65768,65626,65684,65730,65602,65972,65617,65337,
65925,65415,65619,65342,65417,65221,65791,65353,65553,65441,65269,65392,65444,65096,65662,65880,
65468,65403,65677,65525,65471,65610,65291,65600,65508,65614,65286,65813,65393,65505,65520,65980,
65659,65241,65306,65726,65703,65818,65429,65851,65513,65597,65357,65427,65225,65523,65141,65584,
65165,66010,65415,65456,65706,65765,65341,65126,65318,65620,65418,65283,65083,65543,65637,65321,
65695,65335,65500,65764,65507,65456,65235,65225,65380,65424,65731,65316,65588,65533,65992,65647,
65334,65801,65763,65704,65910,65560,65495,65693,65639,65932,65640,65952,65892,65182,65250,65486,
65696,65409,65859,65134,65772,65635,65729,65421,65440,65462,65340,65364,65553,66058,65053,65440,
65603,65068,65347,65808,65732,65696,65326,65552,65334,65650,65601,65839,65608,65135,65425,65872,
65216,65362,65410,65376,65691,66041,65409,65416,65493,65351,65686,65194,65858,65442,65391,65200,
65578,65543,65558,65684,65223,65795,65663,65377,65414,65630,65752,65357,65794,65723,65718,65905

最も少なく出た値が52の64894、最も多く出た値が34の66012でした。 しかし、65536と比べて何れも±1%未満です。 少なくとも長い目で見れば偏りは殆ど存在しないと言えます。

それでは短期的にはどのくらい偏るのでしょうか。 アークドラゴンの場合、発生させた乱数が0〜31の場合に炎を吐きます(つまり1/8の確率です)。 そこでもう1度乱数を16777216回発生させて、今度は0〜31の値が連続した回数を調べてみました。

連続回数
11606698
2201095
324901
43106
5363
649
75
81

大体(n連続):(n+1連続以上)=7:1の、望ましい回数になっています。 そんなに連続で0〜31が出やすいアルゴリズムとは言えないでしょう。 短期的には連続で炎を吐くことがあっても、他のどこかで長いターンの間炎を吐かない時がある筈なのです(実際は、乱数は1ターンの内でもランダムな処理が必要になったモンスターの数だけ生成されるので、連続してドラゴン系の炎の判定に使われることは少ないですが)。

ということで、個人的には偏る乱数は存在せず、単に確率の高いことよりも低いことの方が印象に残りやすいだけだと思います。

2009年2月25日更新

裏CHUNSOFT > ROMイメージ改造・解析 > 乱数生成