自分だけのクイズ作成 - Quipha公開中

【天鳳】牌譜の乱数種から牌山の生成

C++
スポンサーリンク

はじめに

今回は、天鳳の対局後の牌譜から、牌山の生成ができるのか検証してみます🙂

天鳳の場合、乱数種から牌山を生成するので、

予め牌の順番は決まっているそうな🤔

対局中に、例えば「リーチ一発の当たり牌が仕込まれる」とか、「裏ドラが乗りやすい」などの牌操作はあり得ないことが分かります。

これが検証できると、対局の途中で牌山が操作されていないことが証明できます。

参考

以下の記事のソースを参考にしています。
本記事では、設定からビルドするまでの手順をメインに解説しています。

牌山生成検証コード(完成) - integral001
ニコ動、自転車、早見さん、アウトドア、サッカー、麻雀などについて

開発環境

以下の環境でプログラムを実装します。

環境
  • Windows 10
  • Visual Studio 2019
  • C++

OpenSSLが必要ですので、以下の手順を先に行ってください。

インクルードファイルの準備

メルセンヌ・ツイスタ

天鳳では、疑似乱数生成器にメルセンヌ・ツイスタを使用しています。
私はメルセンヌ・ツイスタについて、詳しくないので解説は省略します。

以下のサイトからソースをダウンロードします。

mt19937ar: Mersenne Twister with improved initialization

ヘッダファイルが同梱されている、mt19937ar.sep.tgzをダウンロードします。

ダウンロードした、mt19937ar.sep.tgzファイルを解凍します。
以下のファイルの拡張子を変更します。

mt19937ar.c → mt19937ar.cpp

mt19937ar.cppとmt19937ar.hを以下のように配置します。

ConsoleApplication1\
├─ConsoleApplication1\
│ ├─mt19937ar.cpp
│ └─mt19937ar.h
└─ConsoleApplication1.sln

プロジェクトにファイルを追加します。
既存の項目を選択して、先程の2つのファイルを追加します。

Base64

以下のサイトを参考にします。

Base64エンコード・デコード
プログラム,Windows,Base64,エンコード,デコード,C++,Visual,64,2013

ヘッダファイルを作成しましょう。

base64.hを追加しました。
今回はデコードだけ使用しますので、中身は以下です。

int base64Decode(char* src, char* dtc);

同様の手順で、base64.cppを追加しました。
中身は以下です。

#include <stdio.h>
#include <string.h>

//	base64の1文字を6bitの値に変換する

inline int base64_to_6bit(int c) {
	if (c == '=')
		return 0;
	if (c == '/')
		return 63;
	if (c == '+')
		return 62;
	if (c <= '9')
		return (c - '0') + 52;
	if ('a' <= c)
		return (c - 'a') + 26;
	return (c - 'A');
}

//	base64の文字列srcをデコードしてバイナリ値に変換しdtcに格納
//	lenはbase64の文字数
//	変換後のバイト数を返す

int base64Decode(char* src, char* dtc) {
	unsigned o0, o1, o2, o3;
	char* p = dtc;
	for (int n = 0; src[n];) {
		o0 = base64_to_6bit(src[n]);
		o1 = base64_to_6bit(src[n + 1]);
		o2 = base64_to_6bit(src[n + 2]);
		o3 = base64_to_6bit(src[n + 3]);

		*p++ = (o0 << 2) | ((o1 & 0x30) >> 4);
		*p++ = ((o1 & 0xf) << 4) | ((o2 & 0x3c) >> 2);
		*p++ = ((o2 & 0x3) << 6) | o3 & 0x3f;
		n += 4;
	}
	*p = 0;
	return int(p - dtc);
}

牌山生成コードのビルドと実行

以下のコードを参考にして、ビルドと実行を行います。

牌山生成検証コード(完成) - integral001
ニコ動、自転車、早見さん、アウトドア、サッカー、麻雀などについて

includeは変更しました。

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

↓に変更

#include <iostream>
#include <windows.h>
スポンサーリンク

検証

先日の私の牌譜で牌山を生成してみます。

天鳳 / Web版

有料会員であれば、対局の牌譜のダウンロードは、以下からできます。

牌譜のダウンロード機能が無いクライアント(Flash版Web版アプリ版)で打った対戦の牌譜でもダウンロードすることができます。 ダウンロードできるのは、過去10日以内の「有料会員の状態で打った牌譜」に限られます。

https://tenhou.net/mjlog.html
オンライン対戦麻雀 天鳳 / HTML5+JS版牌譜ビューアβ

自分のIDを入力し、ダウンロードリンクから、mjlogファイルを取得することができます。

ダウンロードしたファイルはgzip圧縮されていますので、拡張子を変更して解凍します。

ファイルをテキストエディタで開くと、XML形式のデータを確認できます。
先頭部分に、この対局で使用した乱数種が記載されています。

<mjloggm ver="2.3"><SHUFFLE seed="mt19937ar-sha512-n288-base64,乱数種" ref=""/>

上記の乱数種をコピペし、ソースに貼り付けて、実行してみます。

char MTseed_b64[] = "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";

一局目

——–Game 0——–
yama =
<東><4p><發><8s><3p><3m><白><4m><9m><5m><7m><發><9s><8m><6s><5p><6m>
<9s><南><3m><4s><1s><4m><6p><5m><8p><1m><東><9p><6s><5p><6p><4s><9p>
<7s><7s><中><2m><3s><1m><5s><北><2m><4s><1p><3s><8s><發><1s><7p><3s>
<南><8p><9p><2s><南><2s><東><7m><6m><6s><6p><2s><北><9s><2m><8s><西>
<1m><南><5s><1s><9m><北><5s><6p><7s><2p><2s><1s><6m><中><白><發> <3m>
<3p><3s><白><7p><西><9m><西><3m><9m><5s><5p><東><2p><1p><7m><8m><2m>
<4m><5m><北><白><6s><8p><9s><4p><1m><4p><西><5m><1p><6m><7s><7p><4p>
<3p><1p><7m><8m><4s><5p><8m><9p><8s><8p><中><4m><2p><2p><3p><7p><中>

dice0 = 2, dice1 = 4

生成された結果が、王牌から順番になっていることが確認できます。
また、サイコロの目が6なので、Bさんの山が王牌になっています。

二局目

——–Game 1——–
yama =
<4s><1s><北><白><東><1p><9p><西><2s><3s><發><7p><東><8p><西><8p><1m>
<5p><9m><2p><7p><2s><3s><3m><1m><9m><2s><中><南><4m><1m><5m><北><5m>
<1p><9p><5m><5m><2m><8m><8s><4s><6s><8s><發><3s><8s><7p><5s><9m><7p>
<7s><7m><7m><4p><4p><5p><東><9s><6s><6m><5p><3p><4m><5s><2s><2p><南>
<9m><北><1p><6m><東><南><3p><1s><7s><9s><白><6p><9s><中><2m><中> <發>
<4p><3m><7s><4m><中><7s><4m><西><5s><5s><6p><西><1p><6p><2m><5p><白>
<1s><3p><3m><7m><8s><南><3m><8m><6p><8p><9s><6s><白><1m><北><6m><7m>
<8p><2p><4p><9p><3p><8m><9p><發><2m><6s><6m><4s><8m><1s><2p><3s><4s>

dice0 = 5, dice1 = 1

二局目も正しく生成されていることが確認できました。

さいごに

今回の検証により、対局後の牌譜の乱数種から牌山を生成することができました👍

「他家リーチに対して、一発を振り込みやすい」とか、局の展開によってツモ順を変更されることはありえないということになります。

「牌操作をした後に乱数種を作ってるんじゃないの?」という意見も聞こえそうですが、この乱数種は事前にハッシュ値が公開されており不可能です。

牌山を生成する情報は、対局前に公開されています。(ハッシュ化されていますが)
つまり、対局前には全て牌山は決まっています。

ということで、天鳳やろっと✋

コメント

タイトルとURLをコピーしました