POCKET ARCADEのデイリーゲーム(ブレイン・スキャン、Astra Tiles、COLOR CODE)は、同じ日に遊ぶ人全員に同じ問題を出しています。ところが、問題をサーバーから配っているわけではありません。問題はすべて、遊ぶ人のブラウザの中でその場で作られています。それでも全員が同じ問題になるのはなぜか——この記事では、その仕組みである「日付シード」と「疑似乱数」を、実際のコードとあわせて解説します。
Math.random() では同じ問題を作れない
JavaScriptでランダムな数を作るときによく使うMath.random()は、呼び出すたびに予測できない数を返します。同じページを2人が開いても、出てくる数はまったく違います。デイリーゲームの問題作りには、これでは困ります。
そこで使うのが疑似乱数です。疑似乱数は、最初に与える数(シード)が同じなら、何度やり直しても、どの端末で動かしても、まったく同じ数の並びを返す計算式です。見た目はランダムでも、中身は決まった計算なのです。
仕組みは3ステップ
- 今日の日付を文字列にする:例「2026-10-10」
- 日付の文字列から数(シード)を作る:文字列を「ハッシュ関数」に通して、32ビットの整数にする
- シードから疑似乱数を作り、問題を生成する:同じシードからは同じ乱数の並びが出るので、同じ問題になる
日付が変われば文字列が変わり、シードが変わり、問題も変わります。サーバーに問い合わせる必要がないので、通信できない環境でも同じ問題を遊べます。
実際のコード
ブレイン・スキャンでは、ハッシュ関数に「FNV-1a」、疑似乱数に「mulberry32」という、どちらも短くて高速なアルゴリズムを使っています。
// 文字列 → 32ビット整数(FNV-1a)
function hashString(str) {
let h = 2166136261;
for (let i = 0; i < str.length; i++) {
h ^= str.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
// シード → 0以上1未満の疑似乱数を返す関数(mulberry32)
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;
};
}
const seed = hashString('brain-scan:' + '2026-10-10');
const rng = mulberry32(seed);
rng(); // 何度実行しても、同じ日なら同じ数の並びになる
シードの文字列に「brain-scan:」のようなゲーム名を付けているのは、同じ日付でもゲームごとに違う問題にするためです。COLOR CODEでは、同じ考え方で「xmur3」というハッシュ関数とmulberry32を組み合わせています。
つまずきやすい4つのポイント
1. 乱数を使う順番を変えてはいけない
疑似乱数は「何回目に呼んだか」で返す数が決まります。問題を作る途中で、ある人の環境だけ余分に乱数を呼んでしまうと、それ以降の問題がずれてしまいます。たとえば「画面が小さいときだけ乱数で位置を調整する」といった処理を問題生成の途中に入れると、端末によって問題が変わる原因になります。
ブレイン・スキャンでは、5つのテストそれぞれに別のシード(日付のシード+テスト番号ごとのずらし)を与えています。こうすると、1つ目のテストで乱数を使う回数がプレイヤーの操作によって変わっても、2つ目以降のテストの問題には影響しません。
2. 「今日」はタイムゾーンで変わる
日付を作るときに、端末の時刻(ローカル時刻)を使うか、日本時間に固定するかで、日付が切り替わる瞬間が変わります。海外から遊ぶ人に日本時間の0時で切り替えたい場合は、日本時間に変換してから日付の文字列を作る必要があります。Astra Tilesは日本時間で日付を決めています。どちらを選ぶかは、「誰に、いつ同じ問題を遊んでほしいか」で決めます。
3. 「1日1回」は端末に記録する
問題が日付で決まる以上、同じ日に何度でも遊び直せてしまいます。1日1回にするには、遊び終えた日付をブラウザ(localStorage)に記録し、同じ日に再び開いたときは結果だけを表示します。ブレイン・スキャンでは、テストを1つ終えるたびに途中経過も保存し、途中でページを閉じても続きから再開できるようにしています。
4. 問題の「質」は数を作ってから確かめる
乱数で問題を作ると、まれに簡単すぎる・難しすぎる問題が出ることがあります。開発中に何百日分もの問題をまとめて生成し、難しさの偏りがないかを確認しておくと安心です。
この仕組みの良いところ
- サーバーがいらない:問題を配る仕組みを作らなくても、全員に同じ問題を出せる。
- オフラインでも動く:一度ページを読み込めば、通信なしで問題を作れる。
- 過去の問題を再現できる:日付さえわかれば、何日前の問題でも同じものを作り直せる。
一方で、問題の作り方(コード)を変えると、同じ日付でも問題が変わってしまいます。デイリーゲームを公開したあとに問題生成のコードを変更するときは、その日の途中で問題が変わらないよう、切り替えのタイミングに注意が必要です。
まとめ
- 日付の文字列をハッシュ関数でシードにし、疑似乱数で問題を作ると、全員が同じ問題になる
- 乱数を呼ぶ順番は固定する。テストごとにシードを分けると安全
- 日付の切り替えをローカル時刻にするか日本時間にするかを決めておく
- 1日1回の制限は、遊んだ日付をブラウザに記録して実現する