マガジン

ゲームの乱数は本当に「偶然」か:擬似乱数の仕組みを体験する

遊びを見つける

執筆にClaudeを使用しています。

分かれ道の前後に駒と橋を置き、サイコロの運とプレイヤーの選択を表したイラスト。
記事のテーマをもとに制作したAI生成イラスト。

サイコロを振るゲームで、負けた直後にリセットして同じ操作をやり直す場面を考えてみます。もしサイコロの目まで前回とそっくり同じ順番で出たら、驚くかもしれません。しかし、ゲームの中の「ランダム」の多くは、実はそうなる仕組みでできています。 結論を先に書くと、ゲームの乱数の多くは物理的な偶然ではなく、計算で作った擬似乱数です。式と出発点(種、シード)が決まれば、出てくる数の列は毎回まったく同じになります。偶然に見えるのは、列に規則が見えにくいからです。 この記事では、まず20行ほどのHTMLで「同じ種なら同じ列」を手元で確かめます。そのうえで、アソバースの公式作品を開き、同じ条件から同じ結果になるかを自分で観察する手順を示します。結果はこちらでは書きません。作品の内部で乱数が使われているかどうかも、確認済みの情報にはありません。だからこそ、外から観察して推理する形にします。

この記事の目次
  1. 擬似乱数は「状態」を更新し続ける計算です
  2. 手元で確かめる:種つきの生成器を動かします
  3. 実際の作品で確かめる:同じ条件を作って見比べます
  4. 結果の読み方と、起こりやすい誤り
  5. 作る人へ:種を残す設計を考えてみます
  6. 次の行動
  7. 参考資料

擬似乱数は「状態」を更新し続ける計算です

擬似乱数の生成器には、内部に状態と呼ばれる数が入っています。乱数を1つ求めるたびに、状態を決まった式で更新し、その状態から0以上1未満の小数などを取り出します。

出発点となる最初の状態が種です。種が同じなら、更新の道筋も取り出される数もすべて同じになります。逆に、種を少し変えると、列はまったく別物に見えます。

この性質には、ゲーム制作上の利点があります。

  • 不具合が出た場面を、同じ種で何度でも再現できます。
  • 「今日のステージ」のように、全員に同じ盤面を配れます。
  • 保存するのは種と操作の記録だけで済み、盤面全体を保存せずに再現できます。

一方、真の乱数との違いもあります。生成器の状態は有限なので、列はいずれ元に戻って繰り返します。ただし、遊びの用途では繰り返しまでの長さは十分に長く取られています。また、種が予想できると先の数も予想できるため、パスワードのような安全が必要な場面には向きません。そうした用途では、ブラウザが提供する暗号用の乱数(crypto.getRandomValues)が使われます。

もう一つ、多くのブラウザの Math.random() は、利用者が種を指定する手段を持ちません。「毎回違うはず」の乱数が欲しいときの道具で、「再現したい乱数」には向きません。再現したいなら、種を受け取れる自前の生成器を使います。

手元で確かめる:種つきの生成器を動かします

次のコードを test.html という名前で保存し、ブラウザで開いてみてください。mulberry32 は短く書ける種つき生成器として広く知られているものです。サーバーも外部ライブラリも使わず、これだけで動きます。

html
<!doctype html>
<meta charset="utf-8">
<pre id="o"></pre>
<script>
function mulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;
let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;
return((t^t>>>14)>>>0)/4294967296;}}
function roll(seed,n){const r=mulberry32(seed);
return Array.from({length:n},()=>Math.floor(r()*6)+1).join(" ");}
const lines=[
 "種42 1回目: "+roll(42,8),
 "種42 2回目: "+roll(42,8),
 "種43      : "+roll(43,8),
 "Math.random: "+Array.from({length:8},()=>Math.floor(Math.random()*6)+1).join(" ")
];
document.getElementById("o").textContent=lines.join("\n");
</script>

各行は、1〜6の目を8回出した列です。見るべき点は次の3つです。

  1. 「種42 1回目」と「種42 2回目」は、仕組み上、必ず同じ列になります。
  2. 「種43」は、種が1つ違うだけで別の列になるのが普通です。サイコロ8回分が偶然すべて一致する確率は非常に小さいからです。
  3. 「Math.random」の行は、ページを再読み込みするたびに変わるのが普通です。

数字の並びを見比べて、種42の列に「規則」が見つかるかも試してみてください。肉眼で規則が見えなくても、同じ種から同じ列が出るという事実は動きません。これが「偶然に見える決定的な計算」です。

実際の作品で確かめる:同じ条件を作って見比べます

ここからは公式作品を使います。ゲームの内部が擬似乱数を使っているか、固定の配置を使っているか、Math.random を使っているかは、画面の外からは判別できません。確かめられるのは「同じ条件から始めたとき、見えるものが同じかどうか」までです。

観察の単位は「1回の開始につき、最初に見える状態を紙にメモする」とし、各作品で3回ずつ繰り返します。

  • かたちのあいだ、同じにする条件:同じ盤の大きさ(3×3など)、見比べるもの:新しく始めたときの初期配置
  • かたちのあいだ、同じにする条件:「同じ配置の再挑戦」、見比べるもの:再挑戦した盤が最初の配置と一致するか
  • こだまの森、同じにする条件:同じコース(6・8・10ラウンドのどれか)、見比べるもの:最初のラウンドで光る葉の順番
  • 紙ひこうき郵便、同じにする条件:同じルート(海辺・丘・山)、見比べるもの:開始直後に見える風船の位置
  • 小さな軌道、同じにする条件:同じ星系、同じ発射角度と初速、見比べるもの:描かれた軌道が前回の軌道の線と重なるか
  • 潮だまり、同じにする条件:同じ海藻と小石の配置、見比べるもの:潮を切り替えたとき現れる生きもの

「設計から読み取れること」を手がかりにします

確認済みの仕様から、あらかじめ読み取れることがあります。

  • かたちのあいだには「同じ配置の再挑戦」があります。つまり、作品は始めた盤の配置を覚えていて、もう一度出し直せます。「新しく始める」と「同じ配置で再挑戦」の違いを見比べると、配置が作られる部分と保たれる部分の境目が見えてきます。
  • 潮だまりでは、上下左右の隣接条件で生きものが現れます。条件で決まる設計なら、同じ配置から同じ生きものが現れるはずです。置く場所を1マスずらすと結果が変わるかも、あわせて見てみてください。
  • 小さな軌道には、予測の点線と前回の軌道の細い実線が用意されています。同じ角度と初速で打ち直し、線が重なるなら、引力による軌道計算が入力から結果を一意に決めていると読めます。ただし、角度と初速を前回とまったく同じ値に合わせられるかは、操作画面によります。ここは作品を開いて確かめてください。
  • こだまの森と紙ひこうき郵便の順番や配置が毎回どうなるかは、確認済み情報にありません。記録を取り、見比べて判断してください。

記録の取り方の例

こだまの森なら、コースを固定して「新しく始める」を3回行い、最初のラウンドで光った葉の位置や順番を、左から1、2、3…のように番号で書き留めます。ゲームを閉じて開き直した直後の分も、1回分に加えてください。最高記録は画面を開いている間だけ残る仕様なので、開き直すと記録は消えますが、この観察には影響しません。

結果の読み方と、起こりやすい誤り

3回の記録を見比べて、次のように読みます。

  • 3回とも同じ:固定の配置か、毎回同じ種を使っている可能性があります。ただし、この二つは見た目では区別できません。
  • 3回とも違う:種が毎回変わっている(時刻などから決める方式がよく使われます)か、種を指定できない乱数を使っている可能性があります。
  • 一部だけ一致:次の誤りを疑ってください。

起こりやすい誤りは三つあります。

  1. 選択肢が少ないことによる偶然の一致です。たとえば音色が4つなら、1回の選択が偶然一致する確率は4分の1です。最初の1ラウンドだけを比べて「同じ」と決めず、ラウンド全体の並びで比べます。
  2. 入力の違いを取り違えることです。小さな軌道で軌道が重ならなくても、乱数とは限りません。角度や初速がわずかに違っただけの可能性があります。
  3. 見えない部分は判断できないことです。画面に出ない内部の乱数がどうなっているかは、観察では分かりません。「見える範囲では同じだった(違った)」と書くのが正確です。

回数が3回では少ないと感じたら、5回に増やしても構いません。増やすほど「偶然の一致」の可能性は下がります。

作る人へ:種を残す設計を考えてみます

自分のゲームに再現できる乱数を入れるなら、考えることは三つです。

  1. 種を1つの数として決めて保持します。生成器の式はコードの短い部分で済みます。種そのものは数値1つなので、保存する量は小さく済みます。
  2. 種をどこに残すかを決めます。アソバースの確認済み事項では、保存があるのは余白の庭・WAVE / WAVE・かたちのあいだの3作品で、他の公式作品の記録は画面を開いている間だけです。アップロード作品の localStorage と sessionStorage も、一時メモリーとしての互換処理です。ログイン中のクラウドセーブを使うには、アップロード作品ならSDKが自動で追加され、URL投稿ならSDKを自分で読み込みます。容量は作品ごとにJSONで128KiBまでなので、数値1つの種は十分に収まります。ゲームにFirebaseのUIDや認証トークン、メールアドレスを渡してはいけません。種の保存にこれらは不要です。
  3. 共有の範囲を決めます。ゲーム内スコアの共通ランキングは提供されていません。「全員に同じ盤面」を配る設計にしても、結果を共通で並べる仕組みは自作になります。種を画面に表示して、遊んだ人どうしで見せ合う程度から始めるのが現実的です。

次の行動

まず、上のHTMLを開いて「種42」の2行が一致することを確かめてください。次に、公式作品のうち気になる一つで、同じ条件から3回始めて、最初の状態を紙にメモします。結果が同じでも違っても、それは見える範囲での観察です。内部の仕組みの結論ではないことを、メモに一言添えておきましょう。

参考資料

  • MDN Web Docs「Math.random()」
  • MDN Web Docs「Crypto: getRandomValues() メソッド」