天鳳の牌譜形式を解析する(1)

天鳳の牌譜は http://tenhou.net/0/log/ から取得することができる。第五期 天鳳名人戦 最終節 最終戦 であれば以下のURLになる。

形式はXMLっぽいが、要素名自体がデータになっていたり入れ子構造がなかったりでXMLの特性を生かしているとは言いがたい。何よりぱっと見て対局内容を想像することができない。天鳳の牌譜で麻雀研究をしようとする場合、この複雑怪奇な牌譜形式が大きな障害になっていると思う*1。そこで天鳳の牌譜を電脳麻将牌譜形式に変換するプログラムをPerlで作ってみた。

XMLをパーズする

標準入力から天鳳の牌譜全体を読み込んでパターンマッチングでパーズする。

for (join('', <>) =~ /<.*?>/g) {
    my ($elem, $attr) = /^<(\/?\w+)(.*?)\/?>$/;
    my %attr = $attr ? ($attr =~ /\s+(\w+)="(.*?)"/g) : ();

    # 以下は要素ごとの処理。

}

上記のコードで $elem に要素名が、%attr に key-value の形式で属性名と属性値が格納される*2。後は要素ごとに処理を行えばよい。

ドキュメントがある訳ではないのですべて推測だけど、各要素とその属性の意味は以下と思う。

mjloggm

全体を囲むタグ。天鳳牌譜のXMLでこれ以外に内容を持つ要素はなく、構造のない要素が直列に並んでいる。

ver
牌譜形式のバージョン。現在は2.3。

SHUFFLE

乱数に関する情報。ここから牌山を一意に決定できるらしい。めくられなかった牌山を含めて対局を再現したい場合はこの情報を利用する必要があると思うが、私は興味がないので使用しない*3

seed
乱数の種。
ref
空文字列。意味は不明。

GO

ルール(赤あり、喰いありなど)、卓(一般、上級、特上、鳳凰)などの情報。

type
以下の8ビットのビットフィールドを10進化した値。

0x01 人間との対戦のとき 1
0x02 赤牌なしのとき 1
0x04 ナシナシのとき 1
0x08 東風戦のとき 0、東南戦のとき 1
0x10 三麻のとき 1
0x20 卓レベル(後述)
0x40 速卓(持ち時間が短い)のとき 1
0x80 卓レベル(後述)

卓レベルはおそらく歴史的経緯でビットが分かれ、かつ反転してしまっているが、0x00: 一般、0x80: 上級、0x20: 特上、0xA0: 鳳凰 の意味。「四特南喰赤」の場合、type="41" となる。

lobby
大会ロビーのID。

UN

天鳳名、段位、レートなど対局者の情報。

n0 〜 n3
天鳳名。UTF8でURLエンコードされている。対局者の番号は仮親を 0 とし、半時計回りで番号を振る。
dan
段位。カンマ( , )区切りで4人分まとめて設定されている。0: 新人 〜 20: 天鳳位。
rate
レート。やはりカンマ区切り。小数点以下の値もあるので要注意。
sx
性別*4

TAIKYOKU

この要素以下が実際の対局ログとなる。

oya
起家。どうやら常に 0 のもよう。席順がランダムなのでこれはこれであり。

INIT

局の開始情報。

seed
カンマ区切りで以下の情報が入っている。局順、本場、供託、開門のサイコロの値1、2、ドラ表示牌。局順は 0が東一局で1が東二局と進んでいく。西一局は8。サイコロの値はなぜか実際の値から1減算されているのでとり得る値は 0〜5。ドラ表示牌はすべての牌に一意に振られた数字(仮に牌番号と呼ぶ)で表されている*5。南三局1本場、供託リーチ棒2本、開門のサイコロの目は 2, 4、ドラ表示牌がm0の場合、seed="6,1,2,1,3,16" となる。
ten
局開始時の持ち点 / 100。カンマ区切りで4人分まとめて設定されている。
oya
親番の対局者の番号。起家が常に0なので、東一局は0となる。
hai0 〜 hai3
配牌。13枚分の情報がカンマ区切りでドラ表示牌と同じ牌番号形式で設定されている。理牌はされていない。

(T|U|V|W)(0〜135)

自摸情報。T: 0番の対局者の自摸 〜 W: 3番の対局者の自摸。0 〜 135: ツモった牌の牌番号。通常の自摸と槓自摸の区別はない。

(D|E|F|G)(0〜135)

打牌情報。D: 0番の対局者の打牌 〜 G: 3番の対局者の打牌。0 〜 135: 打った牌の牌番号。

N

副露情報。

who
副露した対局者の番号。
m
面子を表す数(仮に面子コードと呼ぶ)。解釈方法はとても複雑なので別途説明する。

DORA

開槓情報。

hai
新ドラ表示牌の牌番号。

REACH

リーチ情報。リーチ宣言打牌の直前と直後に出現する。リーチ宣言牌で放銃しリーチ不成立の場合は、直後のタグは出現しない。

who
リーチした対局者の番号。
step
1: リーチ宣言、2: リーチ成立。
ten
リーチ成立後の各対局者の持ち点 / 100。カンマ区切りで4人分まとめて設定されている。

AGARI

和了情報。ダブロンの場合は2回出現する。

ba
積み棒と供託リーチ棒の数。
hai
副露牌を除く手牌(兵牌)。和了牌も含む。カンマ区切り、牌番号形式。
m
副露牌(暗槓含む)。カンマ区切り、面子コード形式。
machi
和了牌。牌番号形式。
ten
符、和了打点、満貫情報をカンマ区切りで連結して設定。ここでの和了打点はなぜか100で割られていない。満貫情報は以下。0: 満貫未満、1: 満貫、2: 跳満、3: 倍満、4: 三倍満、5: 役満
yaku
役満以外場合の和了役情報。役番号、翻数をすべてカンマ区切りで連結して設定されている。役番号は 0: 門前清自摸和 〜 54: 赤ドラ。具体的な値は別途説明する。
yakuman
役満の場合の和了役情報。翻数は不要なので役番号のみがカンマ区切りで連結して設定されている。
doraHai
ドラ表示牌の牌番号をカンマで連結して設定。
doraHaiUra
裏ドラ表示牌の牌番号をカンマで連結して設定。リーチしていない場合はこの属性はない。
who
和了者の番号。
fromWho
放銃者の番号。ツモ和了の場合は和了者の番号が設定される。
sc
和了による点数移動の情報。対局者0の持ち点 / 100、収支 / 100 〜 対局者3の持ち点 / 100、収支 / 100 の8項目をカンマ区切りで連結して設定。
owari
オーラスの和了にだけある属性。終局時の持ち点、ウマを含めたポイント数をカンマ区切りで4人分連結して設定。

RYUUKYOKU

流局情報。

ba
和了情報のそれと同じ。
sc
和了情報のそれと同じ。
type
流局理由。以下の流局の場合のみ設定される。

nm 流し満貫
yao9 九種九牌
kaze4 四風連打
reach4 四家立直
ron3 三家和了
kan4 四槓散了

owari
和了情報のそれと同じ。

長くなったので今回はここまで。各要素を解釈するプログラムと、牌番号、面子コード、役番号については次回に説明します。

*1:意図的に分かりにくくしているフシもあるが

*2:この簡潔さ!やっぱりPerlはすばらしい

*3:天鳳の牌譜では王牌を含め山の中身を見ることができるが、それはここから再現していると思われる

*4:ご存知とは思いますが実際の性別じゃないです

*5:牌番号の表現方法は別途説明する