麻雀サーバー の ver.1.3.5 に「偽ラグ」を発生させる処理を追加しました。
ネット麻雀ではポン・チーなどが可能なときにボタンを表示してユーザーの応答を待つため、間が発生します。 この間のことを俗に「ラグ」と呼びますが、これを見れば「誰かがこの牌を使える」ことが分かるので、「ラグ読み」と呼ばれるネット麻雀特有の戦術に使われてしまいます*1。
「鳴きなし」をオンにすると間を発生させない(その代わり鳴くこともできない)とするアプリが多いですが、それと併用して鳴ける鳴けないに関わらずランダムに間を発生させることでラグ読みしにくくすることも可能です。 このランダムな間は「偽ラグ」と呼ばれていますが、電脳麻将の麻雀サーバーにこの処理を追加しました。
電脳麻将 はGame(サーバ側)とPlayer(クライアント側)が 互いに通信することでゲームを進行 させています。 この通信の応答を読み出すまでの時間を調整することでラグを発生させることができます。 実は元々の実装にもラグはあります。 AIの思考速度はとても速く、概ね0.1秒以内に打牌を選択できるので、AIの思考速度のまま打牌させると目にもとまらぬ速さで局が進行してしまいます。 実際、評価のためにAI同士を対戦させると8時間程度で1000半荘をこなせるので、1局は2~3秒程度という計算になります。 対人戦をこの速度で打たれたら威圧感がありすぎるので、速度調整のためにラグを入れているのです。
call_players(type, msg, timeout) {
/* timeout を適切に設定する */
timeout = this._speed == 0 ? 0
: timeout == null ? this._speed * 200
: timeout;
/* メッセージの種別を保存する */
this._status = type;
/* 応答を保存する配列を初期化する */
this._reply = [];
/* 東家から順に対局者にメッセージを送信する */
for (let l = 0; l < 4; l++) {
let id = this._model.player_id[l];
/* 同期モードの場合は直接メソッド action() を呼び出しする。
非同期モードの場合は setTimrout() で非同期に呼び出しする */
if (this._sync)
this._players[id].action(
msg[l], reply => this.reply(id, reply));
else setTimeout(()=>{
this._players[id].action(
msg[l], reply => this.reply(id, reply));
}, 0);
}
/* 非同期モードの場合は timeout ミリ秒後に next() を呼び出す */
if (! this._sync)
this._timeout_id = setTimeout(()=>this.next(), timeout);
}
timout がラグの時間ですが、引数で指定がある場合はそれに従い、そうでない場合はユーザーの指定した速度に応じて時間を変更しています。 画面上の目盛り1つが 200msに相当します*2。
上記は クライアント側のコード ですが、今回 ネット対戦 に偽ラグを入れるため、サーバ側のコード を修正します。 サーバ側にはネット対戦の「持ち時間 の時間切れ」処理があるためやや複雑ですが、ラグの実装は同じです。
timeout = this._speed == 0 ? 0
: timeout == null ? this._speed * 200 + fakelag(type)
: timeout;
関数 fakelag()
で決定した時間だけ追加のラグを入れています。
偽ラグは本物と見間違えるように入れなければ意味がありません。
頻度も重要ですが、それ以上に長さがまちまちであることが必要と考えました*3。
具体的には 0~1 のランダムな入力 x に対して関数 x24 で 0~1 の出力を得るようにし、それを係数として最大2秒の待ち時間が発生するようにしました。
function fakelag(type) {
if (type != 'dapai') return 0;
return Math.pow(Math.random(), 24) * 1600;
}
打牌に対する応答を読み出すときに、以下のグラフに示す特性でラグが発生します。
実際に動作させてみましたが、期待通りの効果を出しているのではないかと思います。