シード付き擬似乱数を生成する関数のコードを掲載します。ドキュメントは「Xorshiftドキュメント」をご覧下さい。
多くの擬似乱数の関数では、シード(計算の種になる初期の値)を設定して、得られる値をコントロールできます。しかし、JavaScriptの標準ランダム関数「Math.random
」は、シードを設定できません。そのため、実行するごとに、どのよな値が得られるかは分かりません。
ゲームを開発している時には、デバッグの再現性を確保するために、擬似乱数のシードは設定したいところです。シードが設定できるか否かで、デバッグの用意さは大きく変わります。
そこで私は、Xorshiftアルゴリズムを利用した擬似乱数の関数を用意して、これを使っています。
擬似乱数には、様々なアルゴリズムがあります。有名な所では、線形合同法やメルセンヌ・ツイスタなどがあります。
線形合同法は、実装が非常に簡単で計算コストも安いために、非常に多くの場所で利用されていますが、乱数としての質はそれほどよくありません(すぐに同じ周期で値が出るようになる、値の設定によっては偏りが酷い)。
メルセンヌ・ツイスタは、乱数としての質が非常によいのですが、計算コストはかかります。
Xorshiftは、乱数としての質はメルセンヌ・ツイスタほどではないのですがよく、計算コストも安いです。実装も簡単なので、自分で疑似関数を追加するには、非常に向いています。
ファイル構成は以下の通りです。>>ダウンロード
※ 初期化用関数「init」の処理については「Webブラウザ/Node.js 互換関数」をご覧下さい。
'use strict';
(function() {
const _t = init('com.crocro.util');
// Xorshift | シード付き擬似乱数
// new Xors(n);でオブジェクトを作成して使います。
_t.Xorshift = function(n) {
let x, y, z, w;
// シード
this.seed = function(n) {
x = 123456789; y = 362436069; z = 521288629; w = 88675123;
if (typeof n === "number") {w = n;}
}
// ランダム
this.rnd = function() {
const t = x ^ (x << 11);
x = y; y = z; z = w;
return w = (w^(w>>19))^(t^(t>>8));
}
// 初回実行
this.seed(n);
};
// 初期化用関数
function init(p) {
try {return module.exports = {}} catch(e) {}
return ((o, p) => {
p.split('.').forEach(k => o = o[k]||(o[k]={}));
return o})(window, p);
};
})();
この関数は、「const xors = new Xorshift(n)
」のように、Xorshiftオブジェクトを作成してから、そのオブジェクトのメソッド「xors.rnd()
」を利用して値を得ます。シードの値「n
」を省略した場合は、デフォルトの値が利用されます。
「rnd()
」を実行するごとに、内部の値が更新されて、新しい擬似乱数を得られます。得られる値は、JavaScript組み込みの「Math.random()
」とは違い、整数です。
「Math.random()
」は0以上1未満の浮動小数点数が得られ、そこに得たい範囲の数字を掛けて整数化して使います。「Math.floor(Math.random() * 16)
」のようにします。
「xors.rnd()
」で得られる値は、桁の大きな整数です。そのため、剰余を取って利用します。「xors.rnd() % 16
」のようにします。
「Math.random()
」と「xors.rnd()
」では、このように使い方に違いがあります。
擬似乱数は、前に計算した時の値に依存して、次の計算結果が決まります。値が得られる順番は、シードが同じ場合は必ず同じです。完全なランダムではなく、擬似乱数と呼ばれるゆえんです。
Xorshiftオブジェクトは、複数作ることができます。ゲームでは、複数の場所で擬似乱数を用います。複数の擬似乱数オブジェクトを作ることで、攻撃用の擬似乱数、防御用の擬似乱数のように、擬似乱数の対応を分けることができます。
そうすることで、「3回目の攻撃でバグが出た」のように、不具合の検証を容易にできます。
以下、Xorshiftで擬似乱数を多く得てみます。試行回数を100回、1000回、10000回と増やせば、それぞれの値が出た数が均されていきます。
// モジュールの読み込み
const Xors = require('./xorshift.js').Xorshift;
// 実行
const test = (max, veiwRate) => {
const xors = new Xors();
const arr = [];
const sz = 10;
for (let i = 0; i < max; i ++) {
const r = xors.rnd() % sz;
arr[r] = arr[r] ? arr[r] + 1 : 1;
}
console.log('max =', max);
arr.forEach((x, i) => {
console.log(i, '*'.repeat(x * veiwRate));
});
console.log('');
};
test(100, 1);
test(1000, 0.3);
test(10000, 0.03);
出力結果です。
max = 100
0 '**********'
1 '*******'
2 '************'
3 '**********'
4 '*************'
5 '******'
6 '*****************'
7 '********'
8 '*******'
9 '**********'
max = 1000
0 '*****************************'
1 '********************************'
2 '***************************'
3 '******************************'
4 '********************************'
5 '*****************************'
6 '**************************************'
7 '************************'
8 '***************************'
9 '***************************'
max = 10000
0 '******************************'
1 '*****************************'
2 '*****************************'
3 '*****************************'
4 '******************************'
5 '*****************************'
6 '******************************'
7 '******************************'
8 '******************************'
9 '******************************'
// Webブラウザ
const Xors = com.crocro.util.Xorshift;
const xors = new Xors();
console.log(xors.rnd(), xors.rnd(), xors.rnd());
// 597902826 458295558 1779455562
console.log(xors.rnd() % 16, xors.rnd() % 16, xors.rnd() % 16);
// 0 14 6
// Node.js
const Xors = require('./xorshift.js').Xorshift;
const xors = new Xors();
console.log(xors.rnd(), xors.rnd(), xors.rnd());
// 597902826 458295558 1779455562
console.log(xors.rnd() % 16, xors.rnd() % 16, xors.rnd() % 16);
// 0 14 6
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>シード付き擬似乱数 Xorshift - JavaScript実用サンプルコード解説付き</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
</script>
<script src="xorshift.js"></script>
<script src="main.js"></script>
<link rel="stylesheet" href="main.css">
</head>
<body>
<textarea id="src"></textarea>
<button id="exec">実行</button>
<textarea id="dst"></textarea>
</body>
</html>
'use strict';
$(function() {
// 変数の初期化
const $src = $('#src');
const $dst = $('#dst');
const $exec = $('#exec');
// 値のセット
$src.val(String.raw`
const Xors = com.crocro.util.Xorshift;
const xors = new Xors();
console.log(xors.rnd(), xors.rnd(), xors.rnd());
console.log(xors.rnd() % 16, xors.rnd() % 16, xors.rnd() % 16);
`.trim());
// コンソール ログのラップ
const log = console.log;
console.log = function() {
for (let i = 0; i < arguments.length; i ++) {
$dst.append(arguments[i] + ' ');
}
$dst.append('\n');
log.apply(console, arguments);
};
// クリック時の処理
$exec.click(() => {
$dst.empty();
eval($src.val());
});
});
html,
body {
margin: 0;
padding: 0;
}
textarea,
button {
box-sizing: border-box;
width: 100%;
vertical-align:bottom;
}
textarea {
height: 5em;
}
// モジュールの読み込み
const Xors = require('./xorshift.js').Xorshift;
// 実行
const xors = new Xors();
console.log(xors.rnd(), xors.rnd(), xors.rnd());
console.log(xors.rnd() % 16, xors.rnd() % 16, xors.rnd() % 16);