Viewer
牌山生成について
牌山生成の流れ
MJ Arcadeにおける、牌山とサイコロの目の決定方法について説明します。
【1】マッチング前
抽選により、「プレイヤー乱数シード」が決まります。

【2】各局の開始時
「全員のプレイヤー乱数シード」と、「不確定要素」を使って、牌山とサイコロの目を決めます。
牌山生成アルゴリズム
アルゴリズム解説
1 | 「プレイヤー乱数シード」と「不確定要素」から、「乱数の種」を生成する。
|
---|---|
2 |
「乱数の種」を使って、「牌山」を作る。
|
3 | 「乱数の種」を使って、「サイコロの目」を決める。 |
サンプルコード
©SEGA
無断複製・改変を禁じます。商用での利用は行わないでください。
// 座席位置
var Direction = 2;
// 牌山の計算
function calcYama(Pseed1,Pseed2,Pseed3,Pseed4,UEseed){
// 各プレイヤーシード(文字列)を160bitの数値にデコードする
var aPseed160 = string_to_u160x4(Pseed1,Pseed2,Pseed3,Pseed4);
// プレイヤーシードを小さい順に並べて連結し、32bit*20の配列にする
var aSeed32 = connect_seed(aPseed160);
// 不確定要素でxorして、完成形のシードにする
var aSeedComp = xor_seed(aSeed32,UEseed);
// 牌山の初期値として順番にセット
var yama = new Yama();
// 完成したシードを使用して、ランダムに牌山全てを入れ替える
var mt_random = new MtRandom(aSeedComp,20);
for (j=0 ; j<136 ; ++j){
var r = mt_random.rand();
r%=136;
yama.swap(r,j);
}
// サイコロの値を決める
yama.setdice(mt_random.rand()%6,mt_random.rand()%6);
return yama;
}
// 各プレイヤーシード(文字列)を160bitの数値にデコードする
function string_to_u160x4(seed1,seed2,seed3,seed4){
var tbl=new Array();
tbl[0]=string_to_u32(seed1);
tbl[1]=string_to_u32(seed2);
tbl[2]=string_to_u32(seed3);
tbl[3]=string_to_u32(seed4);
return tbl;
}
// 文字列をu32に変換する関数
function string_to_u32(seed){
org_letter="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
// 27文字×6bit = 162bit->32bit×5に変換
// いったん2進数にする
var allbit=new String();
for (i=0 ; i<27 ; ++i){
// 1文字分を2進数に変換
c1=seed.charAt(i);
for (j=0 ; j<64 ; ++j){
c2=org_letter.charAt(j);
if (c1==c2){
num="00000"+j.toString(2);
allbit+=num.substring(num.length-6,num.length);
break;
}
}
}
var tbl=new Array(5);
tbl[0]=parseInt(allbit.substring(0,32),2)>>>0;
tbl[1]=parseInt(allbit.substring(32,64),2)>>>0;
tbl[2]=parseInt(allbit.substring(64,96),2)>>>0;
tbl[3]=parseInt(allbit.substring(96,128),2)>>>0;
tbl[4]=parseInt(allbit.substring(128,160),2)>>>0;
return (tbl);
}
// プレイヤーシードを小さい順に並べて連結し、32bit*20の配列にする
function connect_seed(tbl){
for (i=0 ; i<4 ; i++){
for (j=0 ; j<3 ; j++){
for (k=0 ; k<5 ; k++){
if (tbl[j][k] > tbl[j+1][k]){
// 各要素で小さい順に並べ替える
for (l=0 ; l<5 ; ++l){
var tmp = tbl[j][l];
tbl[j][l] = tbl[j+1][l];
tbl[j+1][l] = tmp;
}
break;
}else if (tbl[j][k] < tbl[j+1][k]){
// 既に小さい順になっているので終了
break;
}
}
}
}
var seed64=new Array(20);
for (j=0 ; j<5 ; j++){
for (i=0 ; i<4 ; i++){
seed64[j*4+i]=tbl[i][j];
}
}
return seed64;
}
// 不確定要素でxorして、完成形のシードにする
function xor_seed(seed640, seed5){
for (i=0 ; i<20 ; i++){
seed640[i]^=seed5;
}
return seed640;
}
// 32bitx32bitかけ算 結果は32bit部分のみ
function Mul32(num1, num2){
var low1=num1&0xffff;
var low2=num2&0xffff;
var up1 =(num1/0x10000)>>>0;
var up2 =(num2/0x10000)>>>0;
return (low1*low2+((low1*up2*0x10000)>>>0)+((low2*up1*0x10000)>>>0))>>>0;
}
// MT乱数
var MtRandom = function( seed , length){
var i = 1;
var j = 0;
var k;
var tmp_seed = 19650218;
var ix;
this.MT_RANDOM_N = 624;
this.index = 0;
this.mt = new Array(this.MT_RANDOM_N);
this.mt[ 0 ] = tmp_seed & 0xffffffff;
for ( ix = 1 ; ix < this.MT_RANDOM_N ; ix++ ){
var tmp1 = ((this.mt[ ix -1 ] ^ (this.mt[ ix -1 ] >>> 30))>>>0);
var tmp2 = Mul32(tmp1,1812433253);
var tmp3 = (tmp2>>>0)+ix;
this.mt[ ix ] = tmp3>>>0;
}
var set_length = length;
if (set_length < this.MT_RANDOM_N)
set_length = this.MT_RANDOM_N;
for ( k = set_length ; k ; k-- ){
var tmp1 = ((this.mt[ i -1 ] ^ (this.mt[ i -1 ] >>> 30))>>>0);
var tmp2 = Mul32(tmp1,1664525);
var tmp3 = (this.mt[ i ] ^ tmp2)>>>0;
this.mt[ i ] = (tmp3 + seed[ j ] + j)>>>0;
if( ++i >= this.MT_RANDOM_N ){
this.mt[ 0 ] = this.mt[ this.MT_RANDOM_N -1 ];
i = 1;
}
if( ++j >= length ){
j = 0;
}
}
for ( k = this.MT_RANDOM_N - 1 ; k!=0 ; k-- ){
var tmp1 = ((this.mt[ i -1 ] ^ (this.mt[ i -1 ] >>> 30))>>>0);
var tmp2 = Mul32(tmp1,1566083941);
var tmp3 = (this.mt[i] ^ tmp2)>>>0;
this.mt[ i ] = (tmp3 -i)>>>0;
if( ++i >= this.MT_RANDOM_N ){
this.mt[ 0 ] = this.mt[ this.MT_RANDOM_N -1 ];
i = 1;
}
}
this.mt[ 0 ] = 0x80000000;
this.rand = function(){
var MT_UPPER_MASK = 0X80000000;
var MT_LOWER_MASK = 0x7fffffff;
var MT_MATRIX_A = 0x9908b0df;
var MT_RANDOM_M = 397;
var idx = this.index;
var read_idx = idx + MT_RANDOM_M;
var next_idx = idx + 1;
if (read_idx >= this.MT_RANDOM_N ){
read_idx -= this.MT_RANDOM_N;
if( next_idx >= this.MT_RANDOM_N ){
next_idx = 0;
}
}
var ret = (((this.mt[ idx ] & MT_UPPER_MASK)>>>0) | ((this.mt[ next_idx ] & MT_LOWER_MASK)>>>0))>>>0;
ret = (((this.mt[ read_idx ] ^ (ret >>> 1))>>>0) ^ ((((- ((ret & 0x1)>>>0) )) & MT_MATRIX_A)>>>0))>>>0;
this.mt[ idx ] = ret;
this.index = next_idx;
ret = (ret ^ (ret >>> 11))>>>0;
ret = (ret ^ (((ret << 7) & 0x9d2c5680)>>>0))>>>0;
ret = (ret ^ (((ret << 15) & 0xefc60000)>>>0))>>>0;
ret = (ret ^ (ret >>> 18))>>>0;
return ret;
}
};
// 牌山
var Yama = function(){
this.yama = new Array();
for (i=0 ; i<136 ; ++i){
this.yama[i]=i;
}
this.dice = new Array();
this.dice[0]=0;
this.dice[1]=0;
this.swap = function(i,j){
var tmp=this.yama[i];
this.yama[i]=this.yama[j];
this.yama[j]=tmp;
}
this.setdice = function(i,j) {
this.dice[0]=i;
this.dice[1]=j;
};
}
プレイヤー乱数シード
抽選により決定する値です。
抽選は、プレイヤーが「ゲーム開始」または「コンティニュー」を選択したタイミングで行います。
生成方法については、セキュリティ上の理由により非公開となっています。
不確定要素
対局者の行動によって変化する値です。以下の行動によって、値が増加します。
- 対局者が打牌する
- 配牌
対局者の打牌による増加
打牌によって増える値=(何枚目の捨牌か?) *(牌種)
牌種 | 増加する値 | |
---|---|---|
数牌 | 数字をそのまま加算 | |
字牌 | 東~北 | 1~4 |
白發中 | 5~7 |
3 * 7 = 21
が加算されます。

例1の「中」がポンされた場合、次の打牌も「3枚目の打牌」として扱います。
2筒を切った場合、3 * 2 =6が加算されます。

配牌での増加
配牌のタイミングで、「1」を加算します。
※親の天和や九種九牌などで、打牌がないまま局が終了し、不確定要素が前局と同じ値になるのを防ぐため。
乱数表示の確認方法
「プレイヤー乱数シード」と「不確定要素」は、筐体やMJ.NETで確認することができます。
筐体での確認方法
対局中の「試合状況タブ」や「コンティニュー画面」で表示される、「乱数情報」をタッチしている間、「プレイヤー乱数シード」と「不確定要素」を確認することができます。
MJ.NETでの確認方法
「対戦履歴」のページから、各試合の「乱数情報」を確認することができます。
- アクセス方法
-
- MJ.NET
- プレイデータ
- 対戦履歴
- 乱数情報
※「対戦履歴」の確認は、MJ.NETの有料会員限定のサービスです。
「乱数情報」の表示制限
不正行為防止のため、筐体およびMJ.NETでの「乱数情報」の表示が制限される場合があります。
対局者全員の試合終了が確認できない場合
「対局途中で中断」したり、「通信障害が発生」する等した場合は、「乱数情報」を表示しません。
※先に終了したプレイヤーが乱数情報を悪用する恐れがあるため
Ver.A+以前のアルゴリズムを採用する場合
「MJ5Ver.A+以前のバージョンとマッチングした場合」および「店内対戦」では、「乱数情報」を表示しません。
※MJ5Ver.A以前の牌山生成アルゴリズムは、公開すると不正される恐れがあるため、非公開となっています。
- MJ Arcadeホーム
- 牌山生成について